feat: godot-engine-source-4.3-stable
This commit is contained in:
parent
c59a7dcade
commit
7125d019b5
11149 changed files with 5070401 additions and 0 deletions
2384
engine/editor/import/3d/collada.cpp
Normal file
2384
engine/editor/import/3d/collada.cpp
Normal file
File diff suppressed because it is too large
Load diff
577
engine/editor/import/3d/collada.h
Normal file
577
engine/editor/import/3d/collada.h
Normal file
|
|
@ -0,0 +1,577 @@
|
|||
/**************************************************************************/
|
||||
/* collada.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 COLLADA_H
|
||||
#define COLLADA_H
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/xml_parser.h"
|
||||
#include "core/templates/rb_map.h"
|
||||
#include "scene/resources/material.h"
|
||||
|
||||
class Collada {
|
||||
public:
|
||||
enum ImportFlags {
|
||||
IMPORT_FLAG_SCENE = 1,
|
||||
IMPORT_FLAG_ANIMATION = 2
|
||||
};
|
||||
|
||||
struct Image {
|
||||
String path;
|
||||
};
|
||||
|
||||
struct Material {
|
||||
String name;
|
||||
String instance_effect;
|
||||
};
|
||||
|
||||
struct Effect {
|
||||
String name;
|
||||
HashMap<String, Variant> params;
|
||||
|
||||
struct Channel {
|
||||
int uv_idx = 0;
|
||||
String texture;
|
||||
Color color;
|
||||
Channel() {}
|
||||
};
|
||||
|
||||
Channel diffuse, specular, emission, bump;
|
||||
float shininess = 40;
|
||||
bool found_double_sided = false;
|
||||
bool double_sided = true;
|
||||
bool unshaded = false;
|
||||
|
||||
String get_texture_path(const String &p_source, Collada &p_state) const;
|
||||
|
||||
Effect() {
|
||||
diffuse.color = Color(1, 1, 1, 1);
|
||||
}
|
||||
};
|
||||
|
||||
struct CameraData {
|
||||
enum Mode {
|
||||
MODE_PERSPECTIVE,
|
||||
MODE_ORTHOGONAL
|
||||
};
|
||||
|
||||
Mode mode = MODE_PERSPECTIVE;
|
||||
|
||||
union {
|
||||
struct {
|
||||
float x_fov = 0;
|
||||
float y_fov = 0;
|
||||
} perspective;
|
||||
struct {
|
||||
float x_mag = 0;
|
||||
float y_mag = 0;
|
||||
} orthogonal;
|
||||
};
|
||||
|
||||
float aspect = 1;
|
||||
float z_near = 0.05;
|
||||
float z_far = 4000;
|
||||
|
||||
CameraData() {}
|
||||
};
|
||||
|
||||
struct LightData {
|
||||
enum Mode {
|
||||
MODE_AMBIENT,
|
||||
MODE_DIRECTIONAL,
|
||||
MODE_OMNI,
|
||||
MODE_SPOT
|
||||
};
|
||||
|
||||
Mode mode = MODE_AMBIENT;
|
||||
|
||||
Color color = Color(1, 1, 1, 1);
|
||||
|
||||
float constant_att = 0;
|
||||
float linear_att = 0;
|
||||
float quad_att = 0;
|
||||
|
||||
float spot_angle = 45;
|
||||
float spot_exp = 1;
|
||||
|
||||
LightData() {}
|
||||
};
|
||||
|
||||
struct MeshData {
|
||||
String name;
|
||||
struct Source {
|
||||
Vector<float> array;
|
||||
int stride = 0;
|
||||
};
|
||||
|
||||
HashMap<String, Source> sources;
|
||||
|
||||
struct Vertices {
|
||||
HashMap<String, String> sources;
|
||||
};
|
||||
|
||||
HashMap<String, Vertices> vertices;
|
||||
|
||||
struct Primitives {
|
||||
struct SourceRef {
|
||||
String source;
|
||||
int offset = 0;
|
||||
};
|
||||
|
||||
String material;
|
||||
HashMap<String, SourceRef> sources;
|
||||
Vector<float> polygons;
|
||||
Vector<float> indices;
|
||||
int count = 0;
|
||||
int vertex_size = 0;
|
||||
};
|
||||
|
||||
Vector<Primitives> primitives;
|
||||
|
||||
bool found_double_sided = false;
|
||||
bool double_sided = true;
|
||||
|
||||
MeshData() {}
|
||||
};
|
||||
|
||||
struct CurveData {
|
||||
String name;
|
||||
bool closed = false;
|
||||
|
||||
struct Source {
|
||||
Vector<String> sarray;
|
||||
Vector<float> array;
|
||||
int stride = 0;
|
||||
};
|
||||
|
||||
HashMap<String, Source> sources;
|
||||
|
||||
HashMap<String, String> control_vertices;
|
||||
|
||||
CurveData() {}
|
||||
};
|
||||
|
||||
struct SkinControllerData {
|
||||
String base;
|
||||
bool use_idrefs = false;
|
||||
|
||||
Transform3D bind_shape;
|
||||
|
||||
struct Source {
|
||||
Vector<String> sarray; //maybe for names
|
||||
Vector<float> array;
|
||||
int stride = 1;
|
||||
Source() {}
|
||||
};
|
||||
|
||||
HashMap<String, Source> sources;
|
||||
|
||||
struct Joints {
|
||||
HashMap<String, String> sources;
|
||||
} joints;
|
||||
|
||||
struct Weights {
|
||||
struct SourceRef {
|
||||
String source;
|
||||
int offset = 0;
|
||||
};
|
||||
|
||||
String material;
|
||||
HashMap<String, SourceRef> sources;
|
||||
Vector<float> sets;
|
||||
Vector<float> indices;
|
||||
int count = 0;
|
||||
} weights;
|
||||
|
||||
HashMap<String, Transform3D> bone_rest_map;
|
||||
|
||||
SkinControllerData() {}
|
||||
};
|
||||
|
||||
struct MorphControllerData {
|
||||
String mesh;
|
||||
String mode;
|
||||
|
||||
struct Source {
|
||||
int stride = 1;
|
||||
Vector<String> sarray; //maybe for names
|
||||
Vector<float> array;
|
||||
Source() {}
|
||||
};
|
||||
|
||||
HashMap<String, Source> sources;
|
||||
|
||||
HashMap<String, String> targets;
|
||||
MorphControllerData() {}
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
int idx = 0;
|
||||
Vector3 vertex;
|
||||
Vector3 normal;
|
||||
Vector3 uv;
|
||||
Vector3 uv2;
|
||||
Plane tangent;
|
||||
Color color;
|
||||
int uid = 0;
|
||||
struct Weight {
|
||||
int bone_idx = 0;
|
||||
float weight = 0;
|
||||
bool operator<(const Weight w) const { return weight > w.weight; } //heaviest first
|
||||
};
|
||||
|
||||
Vector<Weight> weights;
|
||||
|
||||
void fix_weights() {
|
||||
weights.sort();
|
||||
if (weights.size() > 4) {
|
||||
//cap to 4 and make weights add up 1
|
||||
weights.resize(4);
|
||||
float total = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
total += weights[i].weight;
|
||||
}
|
||||
if (total) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
weights.write[i].weight /= total;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fix_unit_scale(const Collada &p_state);
|
||||
|
||||
bool operator<(const Vertex &p_vert) const {
|
||||
if (uid == p_vert.uid) {
|
||||
if (vertex == p_vert.vertex) {
|
||||
if (normal == p_vert.normal) {
|
||||
if (uv == p_vert.uv) {
|
||||
if (uv2 == p_vert.uv2) {
|
||||
if (!weights.is_empty() || !p_vert.weights.is_empty()) {
|
||||
if (weights.size() == p_vert.weights.size()) {
|
||||
for (int i = 0; i < weights.size(); i++) {
|
||||
if (weights[i].bone_idx != p_vert.weights[i].bone_idx) {
|
||||
return weights[i].bone_idx < p_vert.weights[i].bone_idx;
|
||||
}
|
||||
|
||||
if (weights[i].weight != p_vert.weights[i].weight) {
|
||||
return weights[i].weight < p_vert.weights[i].weight;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return weights.size() < p_vert.weights.size();
|
||||
}
|
||||
}
|
||||
|
||||
return (color < p_vert.color);
|
||||
} else {
|
||||
return (uv2 < p_vert.uv2);
|
||||
}
|
||||
} else {
|
||||
return (uv < p_vert.uv);
|
||||
}
|
||||
} else {
|
||||
return (normal < p_vert.normal);
|
||||
}
|
||||
} else {
|
||||
return vertex < p_vert.vertex;
|
||||
}
|
||||
} else {
|
||||
return uid < p_vert.uid;
|
||||
}
|
||||
}
|
||||
|
||||
Vertex() {}
|
||||
};
|
||||
|
||||
struct Node {
|
||||
enum Type {
|
||||
TYPE_NODE,
|
||||
TYPE_JOINT,
|
||||
TYPE_SKELETON, //this bone is not collada, it's added afterwards as optimization
|
||||
TYPE_LIGHT,
|
||||
TYPE_CAMERA,
|
||||
TYPE_GEOMETRY
|
||||
};
|
||||
|
||||
struct XForm {
|
||||
enum Op {
|
||||
OP_ROTATE,
|
||||
OP_SCALE,
|
||||
OP_TRANSLATE,
|
||||
OP_MATRIX,
|
||||
OP_VISIBILITY
|
||||
};
|
||||
|
||||
String id;
|
||||
Op op = OP_ROTATE;
|
||||
Vector<float> data;
|
||||
};
|
||||
|
||||
Type type = TYPE_NODE;
|
||||
|
||||
String name;
|
||||
String id;
|
||||
String empty_draw_type;
|
||||
bool noname = false;
|
||||
Vector<XForm> xform_list;
|
||||
Transform3D default_transform;
|
||||
Transform3D post_transform;
|
||||
Vector<Node *> children;
|
||||
|
||||
Node *parent = nullptr;
|
||||
|
||||
Transform3D compute_transform(const Collada &p_state) const;
|
||||
Transform3D get_global_transform() const;
|
||||
Transform3D get_transform() const;
|
||||
|
||||
bool ignore_anim = false;
|
||||
|
||||
Node() {}
|
||||
virtual ~Node() {
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
memdelete(children[i]);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct NodeSkeleton : public Node {
|
||||
NodeSkeleton() { type = TYPE_SKELETON; }
|
||||
};
|
||||
|
||||
struct NodeJoint : public Node {
|
||||
NodeSkeleton *owner = nullptr;
|
||||
String sid;
|
||||
NodeJoint() {
|
||||
type = TYPE_JOINT;
|
||||
}
|
||||
};
|
||||
|
||||
struct NodeGeometry : public Node {
|
||||
bool controller = false;
|
||||
String source;
|
||||
|
||||
struct Material {
|
||||
String target;
|
||||
};
|
||||
|
||||
HashMap<String, Material> material_map;
|
||||
Vector<String> skeletons;
|
||||
|
||||
NodeGeometry() { type = TYPE_GEOMETRY; }
|
||||
};
|
||||
|
||||
struct NodeCamera : public Node {
|
||||
String camera;
|
||||
|
||||
NodeCamera() { type = TYPE_CAMERA; }
|
||||
};
|
||||
|
||||
struct NodeLight : public Node {
|
||||
String light;
|
||||
|
||||
NodeLight() { type = TYPE_LIGHT; }
|
||||
};
|
||||
|
||||
struct VisualScene {
|
||||
String name;
|
||||
Vector<Node *> root_nodes;
|
||||
|
||||
~VisualScene() {
|
||||
for (int i = 0; i < root_nodes.size(); i++) {
|
||||
memdelete(root_nodes[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct AnimationClip {
|
||||
String name;
|
||||
float begin = 0;
|
||||
float end = 1;
|
||||
Vector<String> tracks;
|
||||
|
||||
AnimationClip() {}
|
||||
};
|
||||
|
||||
struct AnimationTrack {
|
||||
String id;
|
||||
String target;
|
||||
String param;
|
||||
String component;
|
||||
bool property = false;
|
||||
|
||||
enum InterpolationType {
|
||||
INTERP_LINEAR,
|
||||
INTERP_BEZIER
|
||||
};
|
||||
|
||||
struct Key {
|
||||
enum Type {
|
||||
TYPE_FLOAT,
|
||||
TYPE_MATRIX
|
||||
};
|
||||
|
||||
float time = 0;
|
||||
Vector<float> data;
|
||||
Point2 in_tangent;
|
||||
Point2 out_tangent;
|
||||
InterpolationType interp_type = INTERP_LINEAR;
|
||||
|
||||
Key() {}
|
||||
};
|
||||
|
||||
Vector<float> get_value_at_time(float p_time) const;
|
||||
|
||||
Vector<Key> keys;
|
||||
|
||||
AnimationTrack() {}
|
||||
};
|
||||
|
||||
/****************/
|
||||
/* IMPORT STATE */
|
||||
/****************/
|
||||
|
||||
struct State {
|
||||
int import_flags = 0;
|
||||
|
||||
float unit_scale = 1.0;
|
||||
Vector3::Axis up_axis = Vector3::AXIS_Y;
|
||||
bool z_up = false;
|
||||
|
||||
struct Version {
|
||||
int major = 0, minor = 0, rev = 0;
|
||||
|
||||
bool operator<(const Version &p_ver) const { return (major == p_ver.major) ? ((minor == p_ver.minor) ? (rev < p_ver.rev) : minor < p_ver.minor) : major < p_ver.major; }
|
||||
Version(int p_major = 0, int p_minor = 0, int p_rev = 0) {
|
||||
major = p_major;
|
||||
minor = p_minor;
|
||||
rev = p_rev;
|
||||
}
|
||||
} version;
|
||||
|
||||
HashMap<String, CameraData> camera_data_map;
|
||||
HashMap<String, MeshData> mesh_data_map;
|
||||
HashMap<String, LightData> light_data_map;
|
||||
HashMap<String, CurveData> curve_data_map;
|
||||
|
||||
HashMap<String, String> mesh_name_map;
|
||||
HashMap<String, String> morph_name_map;
|
||||
HashMap<String, String> morph_ownership_map;
|
||||
HashMap<String, SkinControllerData> skin_controller_data_map;
|
||||
HashMap<String, MorphControllerData> morph_controller_data_map;
|
||||
|
||||
HashMap<String, Image> image_map;
|
||||
HashMap<String, Material> material_map;
|
||||
HashMap<String, Effect> effect_map;
|
||||
|
||||
HashMap<String, VisualScene> visual_scene_map;
|
||||
HashMap<String, Node *> scene_map;
|
||||
HashSet<String> idref_joints;
|
||||
HashMap<String, String> sid_to_node_map;
|
||||
//RBMap<String,NodeJoint*> bone_map;
|
||||
|
||||
HashMap<String, Transform3D> bone_rest_map;
|
||||
|
||||
String local_path;
|
||||
String root_visual_scene;
|
||||
String root_physics_scene;
|
||||
|
||||
Vector<AnimationClip> animation_clips;
|
||||
Vector<AnimationTrack> animation_tracks;
|
||||
HashMap<String, Vector<int>> referenced_tracks;
|
||||
HashMap<String, Vector<int>> by_id_tracks;
|
||||
|
||||
float animation_length = 0;
|
||||
|
||||
State() {}
|
||||
} state;
|
||||
|
||||
Error load(const String &p_path, int p_flags = 0);
|
||||
|
||||
Collada();
|
||||
|
||||
Transform3D fix_transform(const Transform3D &p_transform);
|
||||
|
||||
Transform3D get_root_transform() const;
|
||||
|
||||
int get_uv_channel(const String &p_name);
|
||||
|
||||
private: // private stuff
|
||||
HashMap<String, int> channel_map;
|
||||
|
||||
void _parse_asset(XMLParser &p_parser);
|
||||
void _parse_image(XMLParser &p_parser);
|
||||
void _parse_material(XMLParser &p_parser);
|
||||
void _parse_effect_material(XMLParser &p_parser, Effect &p_effect, String &p_id);
|
||||
void _parse_effect(XMLParser &p_parser);
|
||||
void _parse_camera(XMLParser &p_parser);
|
||||
void _parse_light(XMLParser &p_parser);
|
||||
void _parse_animation_clip(XMLParser &p_parser);
|
||||
|
||||
void _parse_mesh_geometry(XMLParser &p_parser, const String &p_id, const String &p_name);
|
||||
void _parse_curve_geometry(XMLParser &p_parser, const String &p_id, const String &p_name);
|
||||
|
||||
void _parse_skin_controller(XMLParser &p_parser, const String &p_id);
|
||||
void _parse_morph_controller(XMLParser &p_parser, const String &p_id);
|
||||
void _parse_controller(XMLParser &p_parser);
|
||||
|
||||
Node *_parse_visual_instance_geometry(XMLParser &p_parser);
|
||||
Node *_parse_visual_instance_camera(XMLParser &p_parser);
|
||||
Node *_parse_visual_instance_light(XMLParser &p_parser);
|
||||
|
||||
Node *_parse_visual_node_instance_data(XMLParser &p_parser);
|
||||
Node *_parse_visual_scene_node(XMLParser &p_parser);
|
||||
void _parse_visual_scene(XMLParser &p_parser);
|
||||
|
||||
void _parse_animation(XMLParser &p_parser);
|
||||
void _parse_scene(XMLParser &p_parser);
|
||||
void _parse_library(XMLParser &p_parser);
|
||||
|
||||
Variant _parse_param(XMLParser &p_parser);
|
||||
Vector<float> _read_float_array(XMLParser &p_parser);
|
||||
Vector<String> _read_string_array(XMLParser &p_parser);
|
||||
Transform3D _read_transform(XMLParser &p_parser);
|
||||
String _read_empty_draw_type(XMLParser &p_parser);
|
||||
|
||||
void _joint_set_owner(Collada::Node *p_node, NodeSkeleton *p_owner);
|
||||
void _create_skeletons(Collada::Node **p_node, NodeSkeleton *p_skeleton = nullptr);
|
||||
void _find_morph_nodes(VisualScene *p_vscene, Node *p_node);
|
||||
bool _remove_node(Node *p_parent, Node *p_node);
|
||||
void _remove_node(VisualScene *p_vscene, Node *p_node);
|
||||
void _merge_skeletons2(VisualScene *p_vscene);
|
||||
void _merge_skeletons(VisualScene *p_vscene, Node *p_node);
|
||||
bool _optimize_skeletons(VisualScene *p_vscene, Node *p_node);
|
||||
|
||||
bool _move_geometry_to_skeletons(VisualScene *p_vscene, Node *p_node, List<Node *> *p_mgeom);
|
||||
|
||||
void _optimize();
|
||||
};
|
||||
|
||||
#endif // COLLADA_H
|
||||
1867
engine/editor/import/3d/editor_import_collada.cpp
Normal file
1867
engine/editor/import/3d/editor_import_collada.cpp
Normal file
File diff suppressed because it is too large
Load diff
47
engine/editor/import/3d/editor_import_collada.h
Normal file
47
engine/editor/import/3d/editor_import_collada.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**************************************************************************/
|
||||
/* editor_import_collada.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_IMPORT_COLLADA_H
|
||||
#define EDITOR_IMPORT_COLLADA_H
|
||||
|
||||
#include "editor/import/3d/resource_importer_scene.h"
|
||||
|
||||
class EditorSceneFormatImporterCollada : public EditorSceneFormatImporter {
|
||||
GDCLASS(EditorSceneFormatImporterCollada, 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 = nullptr, Error *r_err = nullptr) override;
|
||||
|
||||
EditorSceneFormatImporterCollada();
|
||||
};
|
||||
|
||||
#endif // EDITOR_IMPORT_COLLADA_H
|
||||
251
engine/editor/import/3d/post_import_plugin_skeleton_renamer.cpp
Normal file
251
engine/editor/import/3d/post_import_plugin_skeleton_renamer.cpp
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/**************************************************************************/
|
||||
/* post_import_plugin_skeleton_renamer.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 "post_import_plugin_skeleton_renamer.h"
|
||||
|
||||
#include "editor/import/3d/scene_import_settings.h"
|
||||
#include "scene/3d/bone_attachment_3d.h"
|
||||
#include "scene/3d/importer_mesh_instance_3d.h"
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#include "scene/animation/animation_player.h"
|
||||
#include "scene/resources/bone_map.h"
|
||||
|
||||
void PostImportPluginSkeletonRenamer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) {
|
||||
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/rename_bones"), true));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/unique_node/make_unique"), true));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::STRING, "retarget/bone_renamer/unique_node/skeleton_name"), "GeneralSkeleton"));
|
||||
}
|
||||
}
|
||||
|
||||
void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options, const HashMap<String, String> &p_rename_map) {
|
||||
// Prepare objects.
|
||||
Object *map = p_options["retarget/bone_map"].get_validated_object();
|
||||
if (!map || !bool(p_options["retarget/bone_renamer/rename_bones"])) {
|
||||
return;
|
||||
}
|
||||
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
|
||||
if (skeleton) {
|
||||
// Rename bones in Skeleton3D.
|
||||
int len = skeleton->get_bone_count();
|
||||
for (int i = 0; i < len; i++) {
|
||||
String current_bone_name = skeleton->get_bone_name(i);
|
||||
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
|
||||
if (new_bone_name) {
|
||||
skeleton->set_bone_name(i, new_bone_name->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rename bones in Skin.
|
||||
{
|
||||
TypedArray<Node> nodes = p_base_scene->find_children("*", "ImporterMeshInstance3D");
|
||||
while (nodes.size()) {
|
||||
ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(nodes.pop_back());
|
||||
Ref<Skin> skin = mi->get_skin();
|
||||
if (skin.is_valid()) {
|
||||
Node *node = mi->get_node(mi->get_skeleton_path());
|
||||
if (node) {
|
||||
Skeleton3D *mesh_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||
if (mesh_skeleton && node == skeleton) {
|
||||
int len = skin->get_bind_count();
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
String current_bone_name = skin->get_bind_name(i);
|
||||
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
|
||||
|
||||
if (new_bone_name) {
|
||||
skin->set_bind_name(i, new_bone_name->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rename bones in AnimationPlayer.
|
||||
{
|
||||
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
|
||||
while (nodes.size()) {
|
||||
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
|
||||
List<StringName> anims;
|
||||
ap->get_animation_list(&anims);
|
||||
for (const StringName &name : anims) {
|
||||
Ref<Animation> anim = ap->get_animation(name);
|
||||
int len = anim->get_track_count();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
|
||||
continue;
|
||||
}
|
||||
String track_path = String(anim->track_get_path(i).get_concatenated_names());
|
||||
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
|
||||
if (node) {
|
||||
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||
if (track_skeleton && track_skeleton == skeleton) {
|
||||
String current_bone_name = anim->track_get_path(i).get_subname(0);
|
||||
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
|
||||
if (new_bone_name) {
|
||||
String new_track_path = track_path + ":" + new_bone_name->value;
|
||||
anim->track_set_path(i, new_track_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rename bones in all Nodes by calling method.
|
||||
{
|
||||
Dictionary rename_map_dict;
|
||||
for (HashMap<String, String>::ConstIterator E = p_rename_map.begin(); E; ++E) {
|
||||
rename_map_dict[E->key] = E->value;
|
||||
}
|
||||
|
||||
TypedArray<Node> nodes = p_base_scene->find_children("*", "BoneAttachment3D");
|
||||
while (nodes.size()) {
|
||||
BoneAttachment3D *attachment = Object::cast_to<BoneAttachment3D>(nodes.pop_back());
|
||||
if (attachment) {
|
||||
attachment->notify_skeleton_bones_renamed(p_base_scene, skeleton, rename_map_dict);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PostImportPluginSkeletonRenamer::internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) {
|
||||
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
|
||||
// Prepare objects.
|
||||
Object *map = p_options["retarget/bone_map"].get_validated_object();
|
||||
if (!map || !bool(p_options["retarget/bone_renamer/rename_bones"])) {
|
||||
return;
|
||||
}
|
||||
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
|
||||
BoneMap *bone_map = Object::cast_to<BoneMap>(map);
|
||||
int len = skeleton->get_bone_count();
|
||||
|
||||
// First, prepare main rename map.
|
||||
HashMap<String, String> main_rename_map;
|
||||
for (int i = 0; i < len; i++) {
|
||||
String bone_name = skeleton->get_bone_name(i);
|
||||
String target_name = bone_map->find_profile_bone_name(bone_name);
|
||||
if (target_name.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
main_rename_map.insert(bone_name, target_name);
|
||||
}
|
||||
|
||||
// Preprocess of renaming bones to avoid to conflict with original bone name.
|
||||
HashMap<String, String> pre_rename_map; // HashMap<skeleton bone name, target(profile) bone name>
|
||||
{
|
||||
Vector<String> solved_name_stack;
|
||||
for (int i = 0; i < len; i++) {
|
||||
String bone_name = skeleton->get_bone_name(i);
|
||||
String target_name = bone_map->find_profile_bone_name(bone_name);
|
||||
if (target_name.is_empty() || bone_name == target_name || skeleton->find_bone(target_name) == -1) {
|
||||
continue; // No conflicting.
|
||||
}
|
||||
|
||||
// Solve conflicting.
|
||||
Ref<SkeletonProfile> profile = bone_map->get_profile();
|
||||
String solved_name = target_name;
|
||||
for (int j = 2; skeleton->find_bone(solved_name) >= 0 || profile->find_bone(solved_name) >= 0 || solved_name_stack.has(solved_name); j++) {
|
||||
solved_name = target_name + itos(j);
|
||||
}
|
||||
solved_name_stack.push_back(solved_name);
|
||||
pre_rename_map.insert(target_name, solved_name);
|
||||
}
|
||||
_internal_process(p_category, p_base_scene, p_node, p_resource, p_options, pre_rename_map);
|
||||
}
|
||||
|
||||
// Main process of renaming bones.
|
||||
{
|
||||
// Apply pre-renaming result to prepared main rename map.
|
||||
Vector<String> remove_queue;
|
||||
for (HashMap<String, String>::Iterator E = main_rename_map.begin(); E; ++E) {
|
||||
if (pre_rename_map.has(E->key)) {
|
||||
remove_queue.push_back(E->key);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < remove_queue.size(); i++) {
|
||||
main_rename_map.insert(pre_rename_map[remove_queue[i]], main_rename_map[remove_queue[i]]);
|
||||
main_rename_map.erase(remove_queue[i]);
|
||||
}
|
||||
_internal_process(p_category, p_base_scene, p_node, p_resource, p_options, main_rename_map);
|
||||
}
|
||||
|
||||
// Make unique skeleton.
|
||||
if (bool(p_options["retarget/bone_renamer/unique_node/make_unique"])) {
|
||||
String unique_name = String(p_options["retarget/bone_renamer/unique_node/skeleton_name"]);
|
||||
ERR_FAIL_COND_MSG(unique_name.is_empty(), "Skeleton unique name cannot be empty.");
|
||||
|
||||
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
|
||||
while (nodes.size()) {
|
||||
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
|
||||
List<StringName> anims;
|
||||
ap->get_animation_list(&anims);
|
||||
for (const StringName &name : anims) {
|
||||
Ref<Animation> anim = ap->get_animation(name);
|
||||
int track_len = anim->get_track_count();
|
||||
for (int i = 0; i < track_len; i++) {
|
||||
String track_path = String(anim->track_get_path(i).get_concatenated_names());
|
||||
Node *orig_node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
|
||||
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
|
||||
while (node) {
|
||||
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||
if (track_skeleton && track_skeleton == skeleton) {
|
||||
if (node == orig_node) {
|
||||
if (anim->track_get_path(i).get_subname_count() > 0) {
|
||||
anim->track_set_path(i, UNIQUE_NODE_PREFIX + unique_name + String(":") + anim->track_get_path(i).get_concatenated_subnames());
|
||||
} else {
|
||||
anim->track_set_path(i, UNIQUE_NODE_PREFIX + unique_name);
|
||||
}
|
||||
} else {
|
||||
if (anim->track_get_path(i).get_subname_count() > 0) {
|
||||
anim->track_set_path(i, UNIQUE_NODE_PREFIX + unique_name + "/" + node->get_path_to(orig_node) + String(":") + anim->track_get_path(i).get_concatenated_subnames());
|
||||
} else {
|
||||
anim->track_set_path(i, UNIQUE_NODE_PREFIX + unique_name + "/" + node->get_path_to(orig_node));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
node = node->get_parent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
skeleton->set_name(unique_name);
|
||||
skeleton->set_unique_name_in_owner(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PostImportPluginSkeletonRenamer::PostImportPluginSkeletonRenamer() {
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/**************************************************************************/
|
||||
/* post_import_plugin_skeleton_renamer.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 POST_IMPORT_PLUGIN_SKELETON_RENAMER_H
|
||||
#define POST_IMPORT_PLUGIN_SKELETON_RENAMER_H
|
||||
|
||||
#include "resource_importer_scene.h"
|
||||
|
||||
class PostImportPluginSkeletonRenamer : public EditorScenePostImportPlugin {
|
||||
GDCLASS(PostImportPluginSkeletonRenamer, EditorScenePostImportPlugin);
|
||||
|
||||
public:
|
||||
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) override;
|
||||
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) override;
|
||||
|
||||
void _internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options, const HashMap<String, String> &p_rename_map);
|
||||
|
||||
PostImportPluginSkeletonRenamer();
|
||||
};
|
||||
|
||||
#endif // POST_IMPORT_PLUGIN_SKELETON_RENAMER_H
|
||||
|
|
@ -0,0 +1,777 @@
|
|||
/**************************************************************************/
|
||||
/* post_import_plugin_skeleton_rest_fixer.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 "post_import_plugin_skeleton_rest_fixer.h"
|
||||
|
||||
#include "editor/import/3d/scene_import_settings.h"
|
||||
#include "scene/3d/bone_attachment_3d.h"
|
||||
#include "scene/3d/importer_mesh_instance_3d.h"
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#include "scene/animation/animation_player.h"
|
||||
#include "scene/resources/bone_map.h"
|
||||
|
||||
void PostImportPluginSkeletonRestFixer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) {
|
||||
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/apply_node_transforms"), true));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/normalize_position_tracks"), true));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/reset_all_bone_poses_after_import"), true));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/overwrite_axis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/keep_global_rest_on_leftovers"), true));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/fix_silhouette/enable", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
|
||||
// TODO: PostImportPlugin need to be implemented such as validate_option(PropertyInfo &property, const Dictionary &p_options).
|
||||
// get_internal_option_visibility() is not sufficient because it can only retrieve options implemented in the core and can only read option values.
|
||||
// r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/filter", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::STRING_NAME, PROPERTY_HINT_ENUM, "Hips,Spine,Chest")), Array()));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/fix_silhouette/filter", PROPERTY_HINT_ARRAY_TYPE, "StringName"), Array()));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "retarget/rest_fixer/fix_silhouette/threshold"), 15));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "retarget/rest_fixer/fix_silhouette/base_height_adjustment", PROPERTY_HINT_RANGE, "-1,1,0.01"), 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
Variant PostImportPluginSkeletonRestFixer::get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
|
||||
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
|
||||
if (p_option.begins_with("retarget/rest_fixer/fix_silhouette/")) {
|
||||
if (!bool(p_options["retarget/rest_fixer/fix_silhouette/enable"])) {
|
||||
if (!p_option.ends_with("enable")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (p_option == "retarget/rest_fixer/keep_global_rest_on_leftovers") {
|
||||
return bool(p_options["retarget/rest_fixer/overwrite_axis"]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) {
|
||||
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
|
||||
// Prepare objects.
|
||||
Object *map = p_options["retarget/bone_map"].get_validated_object();
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
BoneMap *bone_map = Object::cast_to<BoneMap>(map);
|
||||
Ref<SkeletonProfile> profile = bone_map->get_profile();
|
||||
if (!profile.is_valid()) {
|
||||
return;
|
||||
}
|
||||
Skeleton3D *src_skeleton = Object::cast_to<Skeleton3D>(p_node);
|
||||
if (!src_skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_renamed = bool(p_options["retarget/bone_renamer/rename_bones"]);
|
||||
Array filter = p_options["retarget/rest_fixer/fix_silhouette/filter"];
|
||||
bool is_rest_changed = false;
|
||||
|
||||
// Build profile skeleton.
|
||||
Skeleton3D *prof_skeleton = memnew(Skeleton3D);
|
||||
{
|
||||
int prof_bone_len = profile->get_bone_size();
|
||||
// Add single bones.
|
||||
for (int i = 0; i < prof_bone_len; i++) {
|
||||
prof_skeleton->add_bone(profile->get_bone_name(i));
|
||||
prof_skeleton->set_bone_rest(i, profile->get_reference_pose(i));
|
||||
}
|
||||
// Set parents.
|
||||
for (int i = 0; i < prof_bone_len; i++) {
|
||||
int parent = profile->find_bone(profile->get_bone_parent(i));
|
||||
if (parent >= 0) {
|
||||
prof_skeleton->set_bone_parent(i, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get global transform.
|
||||
Transform3D global_transform;
|
||||
if (bool(p_options["retarget/rest_fixer/apply_node_transforms"])) {
|
||||
Node *pr = src_skeleton;
|
||||
while (pr) {
|
||||
Node3D *pr3d = Object::cast_to<Node3D>(pr);
|
||||
if (pr3d) {
|
||||
global_transform = pr3d->get_transform() * global_transform;
|
||||
pr3d->set_transform(Transform3D());
|
||||
}
|
||||
pr = pr->get_parent();
|
||||
}
|
||||
global_transform.origin = Vector3(); // Translation by a Node is not a bone animation, so the retargeted model should be at the origin.
|
||||
}
|
||||
|
||||
// Apply node transforms.
|
||||
if (bool(p_options["retarget/rest_fixer/apply_node_transforms"])) {
|
||||
Vector3 scl = global_transform.basis.get_scale_global();
|
||||
|
||||
Vector<int> bones_to_process = src_skeleton->get_parentless_bones();
|
||||
for (int i = 0; i < bones_to_process.size(); i++) {
|
||||
src_skeleton->set_bone_rest(bones_to_process[i], global_transform.orthonormalized() * src_skeleton->get_bone_rest(bones_to_process[i]));
|
||||
|
||||
src_skeleton->set_bone_pose_position(bones_to_process[i], global_transform.orthonormalized().xform(src_skeleton->get_bone_pose_position(bones_to_process[i])));
|
||||
src_skeleton->set_bone_pose_rotation(bones_to_process[i], global_transform.basis.get_rotation_quaternion() * src_skeleton->get_bone_pose_rotation(bones_to_process[i]));
|
||||
src_skeleton->set_bone_pose_scale(bones_to_process[i], (global_transform.orthonormalized().basis * Basis().scaled(src_skeleton->get_bone_pose_scale((bones_to_process[i])))).get_scale());
|
||||
}
|
||||
|
||||
while (bones_to_process.size() > 0) {
|
||||
int src_idx = bones_to_process[0];
|
||||
bones_to_process.erase(src_idx);
|
||||
Vector<int> src_children = src_skeleton->get_bone_children(src_idx);
|
||||
for (int i = 0; i < src_children.size(); i++) {
|
||||
bones_to_process.push_back(src_children[i]);
|
||||
}
|
||||
src_skeleton->set_bone_rest(src_idx, Transform3D(src_skeleton->get_bone_rest(src_idx).basis, src_skeleton->get_bone_rest(src_idx).origin * scl));
|
||||
src_skeleton->set_bone_pose_position(src_idx, src_skeleton->get_bone_pose_position(src_idx) * scl);
|
||||
}
|
||||
|
||||
// Fix animation.
|
||||
bones_to_process = src_skeleton->get_parentless_bones();
|
||||
{
|
||||
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
|
||||
while (nodes.size()) {
|
||||
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
|
||||
List<StringName> anims;
|
||||
ap->get_animation_list(&anims);
|
||||
for (const StringName &name : anims) {
|
||||
Ref<Animation> anim = ap->get_animation(name);
|
||||
int track_len = anim->get_track_count();
|
||||
for (int i = 0; i < track_len; i++) {
|
||||
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (anim->track_is_compressed(i)) {
|
||||
continue; // Shouldn't occur in internal_process().
|
||||
}
|
||||
|
||||
String track_path = String(anim->track_get_path(i).get_concatenated_names());
|
||||
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
|
||||
ERR_CONTINUE(!node);
|
||||
|
||||
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||
if (!track_skeleton || track_skeleton != src_skeleton) {
|
||||
continue;
|
||||
}
|
||||
|
||||
StringName bn = anim->track_get_path(i).get_subname(0);
|
||||
if (!bn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int bone_idx = src_skeleton->find_bone(bn);
|
||||
int key_len = anim->track_get_key_count(i);
|
||||
if (anim->track_get_type(i) == Animation::TYPE_POSITION_3D) {
|
||||
if (bones_to_process.has(bone_idx)) {
|
||||
for (int j = 0; j < key_len; j++) {
|
||||
Vector3 ps = static_cast<Vector3>(anim->track_get_key_value(i, j));
|
||||
anim->track_set_key_value(i, j, global_transform.basis.xform(ps) + global_transform.origin);
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < key_len; j++) {
|
||||
Vector3 ps = static_cast<Vector3>(anim->track_get_key_value(i, j));
|
||||
anim->track_set_key_value(i, j, ps * scl);
|
||||
}
|
||||
}
|
||||
} else if (bones_to_process.has(bone_idx)) {
|
||||
if (anim->track_get_type(i) == Animation::TYPE_ROTATION_3D) {
|
||||
for (int j = 0; j < key_len; j++) {
|
||||
Quaternion qt = static_cast<Quaternion>(anim->track_get_key_value(i, j));
|
||||
anim->track_set_key_value(i, j, global_transform.basis.get_rotation_quaternion() * qt);
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < key_len; j++) {
|
||||
Basis sc = Basis().scaled(static_cast<Vector3>(anim->track_get_key_value(i, j)));
|
||||
anim->track_set_key_value(i, j, (global_transform.orthonormalized().basis * sc).get_scale());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is_rest_changed = true;
|
||||
}
|
||||
|
||||
// Complement Rotation track for compatibility between different rests.
|
||||
{
|
||||
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
|
||||
while (nodes.size()) {
|
||||
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
|
||||
List<StringName> anims;
|
||||
ap->get_animation_list(&anims);
|
||||
for (const StringName &name : anims) {
|
||||
Ref<Animation> anim = ap->get_animation(name);
|
||||
int track_len = anim->get_track_count();
|
||||
|
||||
// Detect does the animation have skeleton's TRS track.
|
||||
String track_path;
|
||||
bool found_skeleton = false;
|
||||
for (int i = 0; i < track_len; i++) {
|
||||
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
|
||||
continue;
|
||||
}
|
||||
track_path = String(anim->track_get_path(i).get_concatenated_names());
|
||||
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
|
||||
if (node) {
|
||||
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||
if (track_skeleton && track_skeleton == src_skeleton) {
|
||||
found_skeleton = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_skeleton) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Search and insert rot track if it doesn't exist.
|
||||
for (int prof_idx = 0; prof_idx < prof_skeleton->get_bone_count(); prof_idx++) {
|
||||
String bone_name = is_renamed ? prof_skeleton->get_bone_name(prof_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_idx)));
|
||||
if (bone_name.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
int src_idx = src_skeleton->find_bone(bone_name);
|
||||
if (src_idx == -1) {
|
||||
continue;
|
||||
}
|
||||
String insert_path = track_path + ":" + bone_name;
|
||||
int rot_track = anim->find_track(insert_path, Animation::TYPE_ROTATION_3D);
|
||||
if (rot_track == -1) {
|
||||
int track = anim->add_track(Animation::TYPE_ROTATION_3D);
|
||||
anim->track_set_path(track, insert_path);
|
||||
anim->track_set_imported(track, true);
|
||||
anim->rotation_track_insert_key(track, 0, src_skeleton->get_bone_rest(src_idx).basis.get_rotation_quaternion());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix silhouette.
|
||||
Vector<Transform3D> silhouette_diff; // Transform values to be ignored when overwrite axis.
|
||||
silhouette_diff.resize(src_skeleton->get_bone_count());
|
||||
Transform3D *silhouette_diff_w = silhouette_diff.ptrw();
|
||||
LocalVector<Transform3D> pre_silhouette_skeleton_global_rest;
|
||||
for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
|
||||
pre_silhouette_skeleton_global_rest.push_back(src_skeleton->get_bone_global_rest(i));
|
||||
}
|
||||
if (bool(p_options["retarget/rest_fixer/fix_silhouette/enable"])) {
|
||||
Vector<int> bones_to_process = prof_skeleton->get_parentless_bones();
|
||||
while (bones_to_process.size() > 0) {
|
||||
int prof_idx = bones_to_process[0];
|
||||
bones_to_process.erase(prof_idx);
|
||||
Vector<int> prof_children = prof_skeleton->get_bone_children(prof_idx);
|
||||
for (int i = 0; i < prof_children.size(); i++) {
|
||||
bones_to_process.push_back(prof_children[i]);
|
||||
}
|
||||
|
||||
// Calc virtual/looking direction with origins.
|
||||
bool is_filtered = false;
|
||||
for (int i = 0; i < filter.size(); i++) {
|
||||
if (String(filter[i]) == prof_skeleton->get_bone_name(prof_idx)) {
|
||||
is_filtered = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_filtered) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int src_idx = src_skeleton->find_bone(is_renamed ? prof_skeleton->get_bone_name(prof_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_idx))));
|
||||
if (src_idx < 0 || profile->get_tail_direction(prof_idx) == SkeletonProfile::TAIL_DIRECTION_END) {
|
||||
continue;
|
||||
}
|
||||
Vector3 prof_tail;
|
||||
Vector3 src_tail;
|
||||
if (profile->get_tail_direction(prof_idx) == SkeletonProfile::TAIL_DIRECTION_AVERAGE_CHILDREN) {
|
||||
PackedInt32Array prof_bone_children = prof_skeleton->get_bone_children(prof_idx);
|
||||
int children_size = prof_bone_children.size();
|
||||
if (children_size == 0) {
|
||||
continue;
|
||||
}
|
||||
bool exist_all_children = true;
|
||||
for (int i = 0; i < children_size; i++) {
|
||||
int prof_child_idx = prof_bone_children[i];
|
||||
int src_child_idx = src_skeleton->find_bone(is_renamed ? prof_skeleton->get_bone_name(prof_child_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_child_idx))));
|
||||
if (src_child_idx < 0) {
|
||||
exist_all_children = false;
|
||||
break;
|
||||
}
|
||||
prof_tail = prof_tail + prof_skeleton->get_bone_global_rest(prof_child_idx).origin;
|
||||
src_tail = src_tail + src_skeleton->get_bone_global_rest(src_child_idx).origin;
|
||||
}
|
||||
if (!exist_all_children) {
|
||||
continue;
|
||||
}
|
||||
prof_tail = prof_tail / children_size;
|
||||
src_tail = src_tail / children_size;
|
||||
}
|
||||
if (profile->get_tail_direction(prof_idx) == SkeletonProfile::TAIL_DIRECTION_SPECIFIC_CHILD) {
|
||||
int prof_tail_idx = prof_skeleton->find_bone(profile->get_bone_tail(prof_idx));
|
||||
if (prof_tail_idx < 0) {
|
||||
continue;
|
||||
}
|
||||
int src_tail_idx = src_skeleton->find_bone(is_renamed ? prof_skeleton->get_bone_name(prof_tail_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_tail_idx))));
|
||||
if (src_tail_idx < 0) {
|
||||
continue;
|
||||
}
|
||||
prof_tail = prof_skeleton->get_bone_global_rest(prof_tail_idx).origin;
|
||||
src_tail = src_skeleton->get_bone_global_rest(src_tail_idx).origin;
|
||||
}
|
||||
|
||||
Vector3 prof_head = prof_skeleton->get_bone_global_rest(prof_idx).origin;
|
||||
Vector3 src_head = src_skeleton->get_bone_global_rest(src_idx).origin;
|
||||
|
||||
Vector3 prof_dir = prof_tail - prof_head;
|
||||
Vector3 src_dir = src_tail - src_head;
|
||||
|
||||
// Rotate rest.
|
||||
if (Math::abs(Math::rad_to_deg(src_dir.angle_to(prof_dir))) > float(p_options["retarget/rest_fixer/fix_silhouette/threshold"])) {
|
||||
// Get rotation difference.
|
||||
Vector3 up_vec; // Need to rotate other than roll axis.
|
||||
switch (Vector3(abs(src_dir.x), abs(src_dir.y), abs(src_dir.z)).min_axis_index()) {
|
||||
case Vector3::AXIS_X: {
|
||||
up_vec = Vector3(1, 0, 0);
|
||||
} break;
|
||||
case Vector3::AXIS_Y: {
|
||||
up_vec = Vector3(0, 1, 0);
|
||||
} break;
|
||||
case Vector3::AXIS_Z: {
|
||||
up_vec = Vector3(0, 0, 1);
|
||||
} break;
|
||||
}
|
||||
Basis src_b;
|
||||
src_b = src_b.looking_at(src_dir, up_vec);
|
||||
Basis prof_b;
|
||||
prof_b = src_b.looking_at(prof_dir, up_vec);
|
||||
if (prof_b.is_equal_approx(Basis())) {
|
||||
continue; // May not need to rotate.
|
||||
}
|
||||
Basis diff_b = prof_b * src_b.inverse();
|
||||
|
||||
// Apply rotation difference as global transform to skeleton.
|
||||
Basis src_pg;
|
||||
int src_parent = src_skeleton->get_bone_parent(src_idx);
|
||||
if (src_parent >= 0) {
|
||||
src_pg = src_skeleton->get_bone_global_rest(src_parent).basis;
|
||||
}
|
||||
Transform3D fixed_rest = Transform3D(src_pg.inverse() * diff_b * src_pg * src_skeleton->get_bone_rest(src_idx).basis, src_skeleton->get_bone_rest(src_idx).origin);
|
||||
src_skeleton->set_bone_rest(src_idx, fixed_rest);
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust scale base bone height.
|
||||
float base_adjustment = float(p_options["retarget/rest_fixer/fix_silhouette/base_height_adjustment"]);
|
||||
if (!Math::is_zero_approx(base_adjustment)) {
|
||||
StringName scale_base_bone_name = profile->get_scale_base_bone();
|
||||
int src_bone_idx = src_skeleton->find_bone(scale_base_bone_name);
|
||||
Transform3D src_rest = src_skeleton->get_bone_rest(src_bone_idx);
|
||||
src_skeleton->set_bone_rest(src_bone_idx, Transform3D(src_rest.basis, Vector3(src_rest.origin.x, src_rest.origin.y + base_adjustment, src_rest.origin.z)));
|
||||
|
||||
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
|
||||
while (nodes.size()) {
|
||||
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
|
||||
List<StringName> anims;
|
||||
ap->get_animation_list(&anims);
|
||||
for (const StringName &name : anims) {
|
||||
Ref<Animation> anim = ap->get_animation(name);
|
||||
int track_len = anim->get_track_count();
|
||||
for (int i = 0; i < track_len; i++) {
|
||||
if (anim->track_get_path(i).get_subname_count() != 1 || anim->track_get_type(i) != Animation::TYPE_POSITION_3D) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (anim->track_is_compressed(i)) {
|
||||
continue; // Shouldn't occur in internal_process().
|
||||
}
|
||||
|
||||
String track_path = String(anim->track_get_path(i).get_concatenated_names());
|
||||
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
|
||||
ERR_CONTINUE(!node);
|
||||
|
||||
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||
if (!track_skeleton || track_skeleton != src_skeleton) {
|
||||
continue;
|
||||
}
|
||||
|
||||
StringName bn = anim->track_get_path(i).get_concatenated_subnames();
|
||||
if (bn != scale_base_bone_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int key_len = anim->track_get_key_count(i);
|
||||
for (int j = 0; j < key_len; j++) {
|
||||
Vector3 pos = static_cast<Vector3>(anim->track_get_key_value(i, j));
|
||||
pos.y += base_adjustment;
|
||||
anim->track_set_key_value(i, j, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For skin modification in overwrite rest.
|
||||
for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
|
||||
silhouette_diff_w[i] = pre_silhouette_skeleton_global_rest[i] * src_skeleton->get_bone_global_rest(i).affine_inverse();
|
||||
}
|
||||
|
||||
is_rest_changed = true;
|
||||
}
|
||||
|
||||
// Set motion scale to Skeleton if normalize position tracks.
|
||||
if (bool(p_options["retarget/rest_fixer/normalize_position_tracks"])) {
|
||||
int src_bone_idx = src_skeleton->find_bone(profile->get_scale_base_bone());
|
||||
if (src_bone_idx >= 0) {
|
||||
real_t motion_scale = abs(src_skeleton->get_bone_global_rest(src_bone_idx).origin.y);
|
||||
if (motion_scale > 0) {
|
||||
src_skeleton->set_motion_scale(motion_scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite axis.
|
||||
if (bool(p_options["retarget/rest_fixer/overwrite_axis"])) {
|
||||
LocalVector<Transform3D> old_skeleton_rest;
|
||||
LocalVector<Transform3D> old_skeleton_global_rest;
|
||||
for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
|
||||
old_skeleton_rest.push_back(src_skeleton->get_bone_rest(i));
|
||||
old_skeleton_global_rest.push_back(src_skeleton->get_bone_global_rest(i));
|
||||
}
|
||||
|
||||
bool keep_global_rest_leftovers = bool(p_options["retarget/rest_fixer/keep_global_rest_on_leftovers"]);
|
||||
|
||||
// Scan hierarchy and populate a whitelist of unmapped bones without mapped descendants.
|
||||
Vector<int> keep_bone_rest;
|
||||
if (keep_global_rest_leftovers) {
|
||||
Vector<int> bones_to_process = src_skeleton->get_parentless_bones();
|
||||
while (bones_to_process.size() > 0) {
|
||||
int src_idx = bones_to_process[0];
|
||||
bones_to_process.erase(src_idx);
|
||||
Vector<int> src_children = src_skeleton->get_bone_children(src_idx);
|
||||
for (const int &src_child : src_children) {
|
||||
bones_to_process.push_back(src_child);
|
||||
}
|
||||
|
||||
StringName src_bone_name = is_renamed ? StringName(src_skeleton->get_bone_name(src_idx)) : bone_map->find_profile_bone_name(src_skeleton->get_bone_name(src_idx));
|
||||
if (src_bone_name != StringName() && !profile->has_bone(src_bone_name)) {
|
||||
// Scan descendants for mapped bones.
|
||||
bool found_mapped = false;
|
||||
|
||||
Vector<int> decendants_to_process = src_skeleton->get_bone_children(src_idx);
|
||||
while (decendants_to_process.size() > 0) {
|
||||
int desc_idx = decendants_to_process[0];
|
||||
decendants_to_process.erase(desc_idx);
|
||||
Vector<int> desc_children = src_skeleton->get_bone_children(desc_idx);
|
||||
for (const int &desc_child : desc_children) {
|
||||
decendants_to_process.push_back(desc_child);
|
||||
}
|
||||
|
||||
StringName desc_bone_name = is_renamed ? StringName(src_skeleton->get_bone_name(desc_idx)) : bone_map->find_profile_bone_name(src_skeleton->get_bone_name(desc_idx));
|
||||
if (desc_bone_name != StringName() && profile->has_bone(desc_bone_name)) {
|
||||
found_mapped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_mapped) {
|
||||
keep_bone_rest.push_back(src_idx); // No mapped descendants. Add to whitelist.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector<Basis> diffs;
|
||||
diffs.resize(src_skeleton->get_bone_count());
|
||||
Basis *diffs_w = diffs.ptrw();
|
||||
|
||||
Vector<int> bones_to_process = src_skeleton->get_parentless_bones();
|
||||
while (bones_to_process.size() > 0) {
|
||||
int src_idx = bones_to_process[0];
|
||||
bones_to_process.erase(src_idx);
|
||||
Vector<int> src_children = src_skeleton->get_bone_children(src_idx);
|
||||
for (int i = 0; i < src_children.size(); i++) {
|
||||
bones_to_process.push_back(src_children[i]);
|
||||
}
|
||||
|
||||
Basis tgt_rot;
|
||||
StringName src_bone_name = is_renamed ? StringName(src_skeleton->get_bone_name(src_idx)) : bone_map->find_profile_bone_name(src_skeleton->get_bone_name(src_idx));
|
||||
if (src_bone_name != StringName()) {
|
||||
Basis src_pg;
|
||||
int src_parent_idx = src_skeleton->get_bone_parent(src_idx);
|
||||
if (src_parent_idx >= 0) {
|
||||
src_pg = src_skeleton->get_bone_global_rest(src_parent_idx).basis;
|
||||
}
|
||||
|
||||
int prof_idx = profile->find_bone(src_bone_name);
|
||||
if (prof_idx >= 0) {
|
||||
tgt_rot = src_pg.inverse() * prof_skeleton->get_bone_global_rest(prof_idx).basis; // Mapped bone uses reference pose.
|
||||
} else if (keep_global_rest_leftovers && keep_bone_rest.has(src_idx)) {
|
||||
tgt_rot = src_pg.inverse() * old_skeleton_global_rest[src_idx].basis; // Non-Mapped bone without mapped children keeps global rest.
|
||||
}
|
||||
}
|
||||
|
||||
if (src_skeleton->get_bone_parent(src_idx) >= 0) {
|
||||
diffs_w[src_idx] = tgt_rot.inverse() * diffs[src_skeleton->get_bone_parent(src_idx)] * src_skeleton->get_bone_rest(src_idx).basis;
|
||||
} else {
|
||||
diffs_w[src_idx] = tgt_rot.inverse() * src_skeleton->get_bone_rest(src_idx).basis;
|
||||
}
|
||||
|
||||
Basis diff;
|
||||
if (src_skeleton->get_bone_parent(src_idx) >= 0) {
|
||||
diff = diffs[src_skeleton->get_bone_parent(src_idx)];
|
||||
}
|
||||
src_skeleton->set_bone_rest(src_idx, Transform3D(tgt_rot, diff.xform(src_skeleton->get_bone_rest(src_idx).origin)));
|
||||
}
|
||||
|
||||
// Fix animation.
|
||||
{
|
||||
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
|
||||
while (nodes.size()) {
|
||||
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
|
||||
ERR_CONTINUE(!ap);
|
||||
List<StringName> anims;
|
||||
ap->get_animation_list(&anims);
|
||||
for (const StringName &name : anims) {
|
||||
Ref<Animation> anim = ap->get_animation(name);
|
||||
int track_len = anim->get_track_count();
|
||||
for (int i = 0; i < track_len; i++) {
|
||||
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (anim->track_is_compressed(i)) {
|
||||
continue; // Shouldn't occur in internal_process().
|
||||
}
|
||||
|
||||
String track_path = String(anim->track_get_path(i).get_concatenated_names());
|
||||
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
|
||||
ERR_CONTINUE(!node);
|
||||
|
||||
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||
if (!track_skeleton || track_skeleton != src_skeleton) {
|
||||
continue;
|
||||
}
|
||||
|
||||
StringName bn = anim->track_get_path(i).get_subname(0);
|
||||
if (!bn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int bone_idx = src_skeleton->find_bone(bn);
|
||||
|
||||
Transform3D old_rest = old_skeleton_rest[bone_idx];
|
||||
Transform3D new_rest = src_skeleton->get_bone_rest(bone_idx);
|
||||
Transform3D old_pg;
|
||||
Transform3D new_pg;
|
||||
int parent_idx = src_skeleton->get_bone_parent(bone_idx);
|
||||
if (parent_idx >= 0) {
|
||||
old_pg = old_skeleton_global_rest[parent_idx];
|
||||
new_pg = src_skeleton->get_bone_global_rest(parent_idx);
|
||||
}
|
||||
|
||||
int key_len = anim->track_get_key_count(i);
|
||||
if (anim->track_get_type(i) == Animation::TYPE_ROTATION_3D) {
|
||||
Quaternion old_rest_q = old_rest.basis.get_rotation_quaternion();
|
||||
Quaternion new_rest_q = new_rest.basis.get_rotation_quaternion();
|
||||
Quaternion old_pg_q = old_pg.basis.get_rotation_quaternion();
|
||||
Quaternion new_pg_q = new_pg.basis.get_rotation_quaternion();
|
||||
for (int j = 0; j < key_len; j++) {
|
||||
Quaternion qt = static_cast<Quaternion>(anim->track_get_key_value(i, j));
|
||||
anim->track_set_key_value(i, j, new_pg_q.inverse() * old_pg_q * qt * old_rest_q.inverse() * old_pg_q.inverse() * new_pg_q * new_rest_q);
|
||||
}
|
||||
} else if (anim->track_get_type(i) == Animation::TYPE_SCALE_3D) {
|
||||
Basis old_rest_b_inv = old_rest.basis.inverse();
|
||||
Basis new_rest_b = new_rest.basis;
|
||||
Basis old_pg_b = old_pg.basis;
|
||||
Basis new_pg_b = new_pg.basis;
|
||||
Basis old_pg_b_inv = old_pg.basis.inverse();
|
||||
Basis new_pg_b_inv = new_pg.basis.inverse();
|
||||
for (int j = 0; j < key_len; j++) {
|
||||
Basis sc = Basis().scaled(static_cast<Vector3>(anim->track_get_key_value(i, j)));
|
||||
anim->track_set_key_value(i, j, (new_pg_b_inv * old_pg_b * sc * old_rest_b_inv * old_pg_b_inv * new_pg_b * new_rest_b).get_scale());
|
||||
}
|
||||
} else {
|
||||
Vector3 old_rest_o = old_rest.origin;
|
||||
Vector3 new_rest_o = new_rest.origin;
|
||||
Basis old_pg_b = old_pg.basis;
|
||||
Basis new_pg_b = new_pg.basis;
|
||||
for (int j = 0; j < key_len; j++) {
|
||||
Vector3 ps = static_cast<Vector3>(anim->track_get_key_value(i, j));
|
||||
anim->track_set_key_value(i, j, new_pg_b.xform_inv(old_pg_b.xform(ps - old_rest_o)) + new_rest_o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (p_options.has("retarget/rest_fixer/reset_all_bone_poses_after_import") && !bool(p_options["retarget/rest_fixer/reset_all_bone_poses_after_import"])) {
|
||||
// If Reset All Bone Poses After Import is disabled, preserve the original bone pose, adjusted for the new bone rolls.
|
||||
for (int bone_idx = 0; bone_idx < src_skeleton->get_bone_count(); bone_idx++) {
|
||||
Transform3D old_rest = old_skeleton_rest[bone_idx];
|
||||
Transform3D new_rest = src_skeleton->get_bone_rest(bone_idx);
|
||||
Transform3D old_pg;
|
||||
Transform3D new_pg;
|
||||
int parent_idx = src_skeleton->get_bone_parent(bone_idx);
|
||||
if (parent_idx >= 0) {
|
||||
old_pg = old_skeleton_global_rest[parent_idx];
|
||||
new_pg = src_skeleton->get_bone_global_rest(parent_idx);
|
||||
}
|
||||
|
||||
Quaternion old_pg_q = old_pg.basis.get_rotation_quaternion();
|
||||
Quaternion new_pg_q = new_pg.basis.get_rotation_quaternion();
|
||||
Quaternion qt = src_skeleton->get_bone_pose_rotation(bone_idx);
|
||||
src_skeleton->set_bone_pose_rotation(bone_idx, new_pg_q.inverse() * old_pg_q * qt * old_rest.basis.get_rotation_quaternion().inverse() * old_pg_q.inverse() * new_pg_q * new_rest.basis.get_rotation_quaternion());
|
||||
|
||||
Basis sc = Basis().scaled(src_skeleton->get_bone_pose_scale(bone_idx));
|
||||
src_skeleton->set_bone_pose_scale(bone_idx, (new_pg.basis.inverse() * old_pg.basis * sc * old_rest.basis.inverse() * old_pg.basis.inverse() * new_pg.basis * new_rest.basis).get_scale());
|
||||
Vector3 ps = src_skeleton->get_bone_pose_position(bone_idx);
|
||||
src_skeleton->set_bone_pose_position(bone_idx, new_pg_q.xform_inv(old_pg_q.xform(ps - old_rest.origin)) + new_rest.origin);
|
||||
}
|
||||
}
|
||||
|
||||
is_rest_changed = true;
|
||||
}
|
||||
|
||||
// Scale position tracks by motion scale if normalize position tracks.
|
||||
if (bool(p_options["retarget/rest_fixer/normalize_position_tracks"])) {
|
||||
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
|
||||
while (nodes.size()) {
|
||||
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
|
||||
List<StringName> anims;
|
||||
ap->get_animation_list(&anims);
|
||||
for (const StringName &name : anims) {
|
||||
Ref<Animation> anim = ap->get_animation(name);
|
||||
int track_len = anim->get_track_count();
|
||||
for (int i = 0; i < track_len; i++) {
|
||||
if (anim->track_get_path(i).get_subname_count() != 1 || anim->track_get_type(i) != Animation::TYPE_POSITION_3D) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (anim->track_is_compressed(i)) {
|
||||
continue; // Shouldn't occur in internal_process().
|
||||
}
|
||||
|
||||
String track_path = String(anim->track_get_path(i).get_concatenated_names());
|
||||
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
|
||||
ERR_CONTINUE(!node);
|
||||
|
||||
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||
if (!track_skeleton || track_skeleton != src_skeleton) {
|
||||
continue;
|
||||
}
|
||||
|
||||
real_t mlt = 1 / src_skeleton->get_motion_scale();
|
||||
int key_len = anim->track_get_key_count(i);
|
||||
for (int j = 0; j < key_len; j++) {
|
||||
Vector3 pos = static_cast<Vector3>(anim->track_get_key_value(i, j));
|
||||
anim->track_set_key_value(i, j, pos * mlt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_rest_changed) {
|
||||
// Fix skin.
|
||||
{
|
||||
HashSet<Ref<Skin>> mutated_skins;
|
||||
TypedArray<Node> nodes = p_base_scene->find_children("*", "ImporterMeshInstance3D");
|
||||
while (nodes.size()) {
|
||||
ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(nodes.pop_back());
|
||||
ERR_CONTINUE(!mi);
|
||||
|
||||
Ref<Skin> skin = mi->get_skin();
|
||||
if (skin.is_null()) {
|
||||
continue;
|
||||
}
|
||||
if (mutated_skins.has(skin)) {
|
||||
continue;
|
||||
}
|
||||
mutated_skins.insert(skin);
|
||||
|
||||
Node *node = mi->get_node(mi->get_skeleton_path());
|
||||
ERR_CONTINUE(!node);
|
||||
|
||||
Skeleton3D *mesh_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||
if (!mesh_skeleton || mesh_skeleton != src_skeleton) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int skin_len = skin->get_bind_count();
|
||||
for (int i = 0; i < skin_len; i++) {
|
||||
StringName bn = skin->get_bind_name(i);
|
||||
int bone_idx = src_skeleton->find_bone(bn);
|
||||
if (bone_idx >= 0) {
|
||||
Transform3D adjust_transform = src_skeleton->get_bone_global_rest(bone_idx).affine_inverse() * silhouette_diff[bone_idx].affine_inverse() * pre_silhouette_skeleton_global_rest[bone_idx];
|
||||
adjust_transform.scale(global_transform.basis.get_scale_global());
|
||||
skin->set_bind_pose(i, adjust_transform * skin->get_bind_pose(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes = src_skeleton->get_children();
|
||||
while (nodes.size()) {
|
||||
BoneAttachment3D *attachment = Object::cast_to<BoneAttachment3D>(nodes.pop_back());
|
||||
if (attachment == nullptr) {
|
||||
continue;
|
||||
}
|
||||
int bone_idx = attachment->get_bone_idx();
|
||||
if (bone_idx == -1) {
|
||||
bone_idx = src_skeleton->find_bone(attachment->get_bone_name());
|
||||
}
|
||||
ERR_CONTINUE(bone_idx < 0 || bone_idx >= src_skeleton->get_bone_count());
|
||||
Transform3D adjust_transform = src_skeleton->get_bone_global_rest(bone_idx).affine_inverse() * silhouette_diff[bone_idx].affine_inverse() * pre_silhouette_skeleton_global_rest[bone_idx];
|
||||
adjust_transform.scale(global_transform.basis.get_scale_global());
|
||||
|
||||
TypedArray<Node> child_nodes = attachment->get_children();
|
||||
while (child_nodes.size()) {
|
||||
Node3D *child = Object::cast_to<Node3D>(child_nodes.pop_back());
|
||||
if (child == nullptr) {
|
||||
continue;
|
||||
}
|
||||
child->set_transform(adjust_transform * child->get_transform());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!p_options.has("retarget/rest_fixer/reset_all_bone_poses_after_import") || bool(p_options["retarget/rest_fixer/reset_all_bone_poses_after_import"])) {
|
||||
// Init skeleton pose to new rest.
|
||||
for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
|
||||
Transform3D fixed_rest = src_skeleton->get_bone_rest(i);
|
||||
src_skeleton->set_bone_pose_position(i, fixed_rest.origin);
|
||||
src_skeleton->set_bone_pose_rotation(i, fixed_rest.basis.get_rotation_quaternion());
|
||||
src_skeleton->set_bone_pose_scale(i, fixed_rest.basis.get_scale());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memdelete(prof_skeleton);
|
||||
}
|
||||
}
|
||||
|
||||
PostImportPluginSkeletonRestFixer::PostImportPluginSkeletonRestFixer() {
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/**************************************************************************/
|
||||
/* post_import_plugin_skeleton_rest_fixer.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 POST_IMPORT_PLUGIN_SKELETON_REST_FIXER_H
|
||||
#define POST_IMPORT_PLUGIN_SKELETON_REST_FIXER_H
|
||||
|
||||
#include "resource_importer_scene.h"
|
||||
|
||||
class PostImportPluginSkeletonRestFixer : public EditorScenePostImportPlugin {
|
||||
GDCLASS(PostImportPluginSkeletonRestFixer, EditorScenePostImportPlugin);
|
||||
|
||||
public:
|
||||
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) override;
|
||||
virtual Variant get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
|
||||
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) override;
|
||||
|
||||
PostImportPluginSkeletonRestFixer();
|
||||
};
|
||||
|
||||
#endif // POST_IMPORT_PLUGIN_SKELETON_REST_FIXER_H
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
/**************************************************************************/
|
||||
/* post_import_plugin_skeleton_track_organizer.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 "post_import_plugin_skeleton_track_organizer.h"
|
||||
|
||||
#include "editor/import/3d/scene_import_settings.h"
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#include "scene/animation/animation_player.h"
|
||||
#include "scene/resources/bone_map.h"
|
||||
|
||||
void PostImportPluginSkeletonTrackOrganizer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) {
|
||||
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/except_bone_transform"), false));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/unimportant_positions"), true));
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/unmapped_bones"), false));
|
||||
}
|
||||
}
|
||||
|
||||
void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) {
|
||||
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
|
||||
// Prepare objects.
|
||||
Object *map = p_options["retarget/bone_map"].get_validated_object();
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
BoneMap *bone_map = Object::cast_to<BoneMap>(map);
|
||||
Ref<SkeletonProfile> profile = bone_map->get_profile();
|
||||
if (!profile.is_valid()) {
|
||||
return;
|
||||
}
|
||||
Skeleton3D *src_skeleton = Object::cast_to<Skeleton3D>(p_node);
|
||||
if (!src_skeleton) {
|
||||
return;
|
||||
}
|
||||
bool remove_except_bone = bool(p_options["retarget/remove_tracks/except_bone_transform"]);
|
||||
bool remove_positions = bool(p_options["retarget/remove_tracks/unimportant_positions"]);
|
||||
bool remove_unmapped_bones = bool(p_options["retarget/remove_tracks/unmapped_bones"]);
|
||||
|
||||
if (!remove_positions && !remove_unmapped_bones) {
|
||||
return;
|
||||
}
|
||||
|
||||
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
|
||||
while (nodes.size()) {
|
||||
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
|
||||
List<StringName> anims;
|
||||
ap->get_animation_list(&anims);
|
||||
for (const StringName &name : anims) {
|
||||
Ref<Animation> anim = ap->get_animation(name);
|
||||
int track_len = anim->get_track_count();
|
||||
Vector<int> remove_indices;
|
||||
for (int i = 0; i < track_len; i++) {
|
||||
String track_path = String(anim->track_get_path(i).get_concatenated_names());
|
||||
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
|
||||
if (!node) {
|
||||
if (remove_except_bone) {
|
||||
remove_indices.push_back(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
|
||||
if (track_skeleton && track_skeleton == src_skeleton) {
|
||||
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
|
||||
if (remove_except_bone) {
|
||||
remove_indices.push_back(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
StringName bn = anim->track_get_path(i).get_subname(0);
|
||||
if (bn) {
|
||||
int prof_idx = profile->find_bone(bone_map->find_profile_bone_name(bn));
|
||||
if (remove_unmapped_bones && prof_idx < 0) {
|
||||
remove_indices.push_back(i);
|
||||
continue;
|
||||
}
|
||||
if (remove_positions && anim->track_get_type(i) == Animation::TYPE_POSITION_3D && prof_idx >= 0) {
|
||||
StringName prof_bn = profile->get_bone_name(prof_idx);
|
||||
if (prof_bn == profile->get_root_bone() || prof_bn == profile->get_scale_base_bone()) {
|
||||
continue;
|
||||
}
|
||||
remove_indices.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (remove_except_bone) {
|
||||
remove_indices.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
remove_indices.reverse();
|
||||
for (int i = 0; i < remove_indices.size(); i++) {
|
||||
anim->remove_track(remove_indices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PostImportPluginSkeletonTrackOrganizer::PostImportPluginSkeletonTrackOrganizer() {
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/**************************************************************************/
|
||||
/* post_import_plugin_skeleton_track_organizer.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 POST_IMPORT_PLUGIN_SKELETON_TRACK_ORGANIZER_H
|
||||
#define POST_IMPORT_PLUGIN_SKELETON_TRACK_ORGANIZER_H
|
||||
|
||||
#include "resource_importer_scene.h"
|
||||
|
||||
class PostImportPluginSkeletonTrackOrganizer : public EditorScenePostImportPlugin {
|
||||
GDCLASS(PostImportPluginSkeletonTrackOrganizer, EditorScenePostImportPlugin);
|
||||
|
||||
public:
|
||||
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) override;
|
||||
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) override;
|
||||
|
||||
PostImportPluginSkeletonTrackOrganizer();
|
||||
};
|
||||
|
||||
#endif // POST_IMPORT_PLUGIN_SKELETON_TRACK_ORGANIZER_H
|
||||
614
engine/editor/import/3d/resource_importer_obj.cpp
Normal file
614
engine/editor/import/3d/resource_importer_obj.cpp
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
/**************************************************************************/
|
||||
/* resource_importer_obj.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 "resource_importer_obj.h"
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "scene/3d/importer_mesh_instance_3d.h"
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
#include "scene/3d/node_3d.h"
|
||||
#include "scene/resources/3d/importer_mesh.h"
|
||||
#include "scene/resources/mesh.h"
|
||||
#include "scene/resources/surface_tool.h"
|
||||
|
||||
uint32_t EditorOBJImporter::get_import_flags() const {
|
||||
return IMPORT_SCENE;
|
||||
}
|
||||
|
||||
static Error _parse_material_library(const String &p_path, HashMap<String, Ref<StandardMaterial3D>> &material_map, List<String> *r_missing_deps) {
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Couldn't open MTL file '%s', it may not exist or not be readable.", p_path));
|
||||
|
||||
Ref<StandardMaterial3D> current;
|
||||
String current_name;
|
||||
String base_path = p_path.get_base_dir();
|
||||
while (true) {
|
||||
String l = f->get_line().strip_edges();
|
||||
|
||||
if (l.begins_with("newmtl ")) {
|
||||
//vertex
|
||||
|
||||
current_name = l.replace("newmtl", "").strip_edges();
|
||||
current.instantiate();
|
||||
current->set_name(current_name);
|
||||
material_map[current_name] = current;
|
||||
} else if (l.begins_with("Ka ")) {
|
||||
//uv
|
||||
WARN_PRINT("OBJ: Ambient light for material '" + current_name + "' is ignored in PBR");
|
||||
|
||||
} else if (l.begins_with("Kd ")) {
|
||||
//normal
|
||||
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
|
||||
Vector<String> v = l.split(" ", false);
|
||||
ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
|
||||
Color c = current->get_albedo();
|
||||
c.r = v[1].to_float();
|
||||
c.g = v[2].to_float();
|
||||
c.b = v[3].to_float();
|
||||
current->set_albedo(c);
|
||||
} else if (l.begins_with("Ks ")) {
|
||||
//normal
|
||||
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
|
||||
Vector<String> v = l.split(" ", false);
|
||||
ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
|
||||
float r = v[1].to_float();
|
||||
float g = v[2].to_float();
|
||||
float b = v[3].to_float();
|
||||
float metalness = MAX(r, MAX(g, b));
|
||||
current->set_metallic(metalness);
|
||||
} else if (l.begins_with("Ns ")) {
|
||||
//normal
|
||||
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
|
||||
Vector<String> v = l.split(" ", false);
|
||||
ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA);
|
||||
float s = v[1].to_float();
|
||||
current->set_metallic((1000.0 - s) / 1000.0);
|
||||
} else if (l.begins_with("d ")) {
|
||||
//normal
|
||||
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
|
||||
Vector<String> v = l.split(" ", false);
|
||||
ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA);
|
||||
float d = v[1].to_float();
|
||||
Color c = current->get_albedo();
|
||||
c.a = d;
|
||||
current->set_albedo(c);
|
||||
if (c.a < 0.99) {
|
||||
current->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
||||
}
|
||||
} else if (l.begins_with("Tr ")) {
|
||||
//normal
|
||||
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
|
||||
Vector<String> v = l.split(" ", false);
|
||||
ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA);
|
||||
float d = v[1].to_float();
|
||||
Color c = current->get_albedo();
|
||||
c.a = 1.0 - d;
|
||||
current->set_albedo(c);
|
||||
if (c.a < 0.99) {
|
||||
current->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
||||
}
|
||||
|
||||
} else if (l.begins_with("map_Ka ")) {
|
||||
//uv
|
||||
WARN_PRINT("OBJ: Ambient light texture for material '" + current_name + "' is ignored in PBR");
|
||||
|
||||
} else if (l.begins_with("map_Kd ")) {
|
||||
//normal
|
||||
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
|
||||
|
||||
String p = l.replace("map_Kd", "").replace("\\", "/").strip_edges();
|
||||
String path;
|
||||
if (p.is_absolute_path()) {
|
||||
path = p;
|
||||
} else {
|
||||
path = base_path.path_join(p);
|
||||
}
|
||||
|
||||
Ref<Texture2D> texture = ResourceLoader::load(path);
|
||||
|
||||
if (texture.is_valid()) {
|
||||
current->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, texture);
|
||||
} else if (r_missing_deps) {
|
||||
r_missing_deps->push_back(path);
|
||||
}
|
||||
|
||||
} else if (l.begins_with("map_Ks ")) {
|
||||
//normal
|
||||
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
|
||||
|
||||
String p = l.replace("map_Ks", "").replace("\\", "/").strip_edges();
|
||||
String path;
|
||||
if (p.is_absolute_path()) {
|
||||
path = p;
|
||||
} else {
|
||||
path = base_path.path_join(p);
|
||||
}
|
||||
|
||||
Ref<Texture2D> texture = ResourceLoader::load(path);
|
||||
|
||||
if (texture.is_valid()) {
|
||||
current->set_texture(StandardMaterial3D::TEXTURE_METALLIC, texture);
|
||||
} else if (r_missing_deps) {
|
||||
r_missing_deps->push_back(path);
|
||||
}
|
||||
|
||||
} else if (l.begins_with("map_Ns ")) {
|
||||
//normal
|
||||
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
|
||||
|
||||
String p = l.replace("map_Ns", "").replace("\\", "/").strip_edges();
|
||||
String path;
|
||||
if (p.is_absolute_path()) {
|
||||
path = p;
|
||||
} else {
|
||||
path = base_path.path_join(p);
|
||||
}
|
||||
|
||||
Ref<Texture2D> texture = ResourceLoader::load(path);
|
||||
|
||||
if (texture.is_valid()) {
|
||||
current->set_texture(StandardMaterial3D::TEXTURE_ROUGHNESS, texture);
|
||||
} else if (r_missing_deps) {
|
||||
r_missing_deps->push_back(path);
|
||||
}
|
||||
} else if (l.begins_with("map_bump ")) {
|
||||
//normal
|
||||
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
|
||||
|
||||
String p = l.replace("map_bump", "").replace("\\", "/").strip_edges();
|
||||
String path = base_path.path_join(p);
|
||||
|
||||
Ref<Texture2D> texture = ResourceLoader::load(path);
|
||||
|
||||
if (texture.is_valid()) {
|
||||
current->set_feature(StandardMaterial3D::FEATURE_NORMAL_MAPPING, true);
|
||||
current->set_texture(StandardMaterial3D::TEXTURE_NORMAL, texture);
|
||||
} else if (r_missing_deps) {
|
||||
r_missing_deps->push_back(path);
|
||||
}
|
||||
} else if (f->eof_reached()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, bool p_single_mesh, bool p_generate_tangents, bool p_optimize, Vector3 p_scale_mesh, Vector3 p_offset_mesh, bool p_disable_compression, List<String> *r_missing_deps) {
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Couldn't open OBJ file '%s', it may not exist or not be readable.", p_path));
|
||||
|
||||
// Avoid trying to load/interpret potential build artifacts from Visual Studio (e.g. when compiling native plugins inside the project tree)
|
||||
// This should only match, if it's indeed a COFF file header
|
||||
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types
|
||||
const int first_bytes = f->get_16();
|
||||
static const Vector<int> coff_header_machines{
|
||||
0x0, // IMAGE_FILE_MACHINE_UNKNOWN
|
||||
0x8664, // IMAGE_FILE_MACHINE_AMD64
|
||||
0x1c0, // IMAGE_FILE_MACHINE_ARM
|
||||
0x14c, // IMAGE_FILE_MACHINE_I386
|
||||
0x200, // IMAGE_FILE_MACHINE_IA64
|
||||
};
|
||||
ERR_FAIL_COND_V_MSG(coff_header_machines.has(first_bytes), ERR_FILE_CORRUPT, vformat("Couldn't read OBJ file '%s', it seems to be binary, corrupted, or empty.", p_path));
|
||||
f->seek(0);
|
||||
|
||||
Ref<ImporterMesh> mesh;
|
||||
mesh.instantiate();
|
||||
|
||||
bool generate_tangents = p_generate_tangents;
|
||||
Vector3 scale_mesh = p_scale_mesh;
|
||||
Vector3 offset_mesh = p_offset_mesh;
|
||||
|
||||
Vector<Vector3> vertices;
|
||||
Vector<Vector3> normals;
|
||||
Vector<Vector2> uvs;
|
||||
Vector<Color> colors;
|
||||
const String default_name = "Mesh";
|
||||
String name = default_name;
|
||||
|
||||
HashMap<String, HashMap<String, Ref<StandardMaterial3D>>> material_map;
|
||||
|
||||
Ref<SurfaceTool> surf_tool = memnew(SurfaceTool);
|
||||
surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
|
||||
|
||||
String current_material_library;
|
||||
String current_material;
|
||||
String current_group;
|
||||
uint32_t smooth_group = 0;
|
||||
bool smoothing = true;
|
||||
const uint32_t no_smoothing_smooth_group = (uint32_t)-1;
|
||||
|
||||
while (true) {
|
||||
String l = f->get_line().strip_edges();
|
||||
while (l.length() && l[l.length() - 1] == '\\') {
|
||||
String add = f->get_line().strip_edges();
|
||||
l += add;
|
||||
if (add.is_empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (l.begins_with("v ")) {
|
||||
//vertex
|
||||
Vector<String> v = l.split(" ", false);
|
||||
ERR_FAIL_COND_V(v.size() < 4, ERR_FILE_CORRUPT);
|
||||
Vector3 vtx;
|
||||
vtx.x = v[1].to_float() * scale_mesh.x + offset_mesh.x;
|
||||
vtx.y = v[2].to_float() * scale_mesh.y + offset_mesh.y;
|
||||
vtx.z = v[3].to_float() * scale_mesh.z + offset_mesh.z;
|
||||
vertices.push_back(vtx);
|
||||
//vertex color
|
||||
if (v.size() >= 7) {
|
||||
while (colors.size() < vertices.size() - 1) {
|
||||
colors.push_back(Color(1.0, 1.0, 1.0));
|
||||
}
|
||||
Color c;
|
||||
c.r = v[4].to_float();
|
||||
c.g = v[5].to_float();
|
||||
c.b = v[6].to_float();
|
||||
colors.push_back(c);
|
||||
} else if (!colors.is_empty()) {
|
||||
colors.push_back(Color(1.0, 1.0, 1.0));
|
||||
}
|
||||
} else if (l.begins_with("vt ")) {
|
||||
//uv
|
||||
Vector<String> v = l.split(" ", false);
|
||||
ERR_FAIL_COND_V(v.size() < 3, ERR_FILE_CORRUPT);
|
||||
Vector2 uv;
|
||||
uv.x = v[1].to_float();
|
||||
uv.y = 1.0 - v[2].to_float();
|
||||
uvs.push_back(uv);
|
||||
} else if (l.begins_with("vn ")) {
|
||||
//normal
|
||||
Vector<String> v = l.split(" ", false);
|
||||
ERR_FAIL_COND_V(v.size() < 4, ERR_FILE_CORRUPT);
|
||||
Vector3 nrm;
|
||||
nrm.x = v[1].to_float();
|
||||
nrm.y = v[2].to_float();
|
||||
nrm.z = v[3].to_float();
|
||||
normals.push_back(nrm);
|
||||
} else if (l.begins_with("f ")) {
|
||||
//vertex
|
||||
|
||||
Vector<String> v = l.split(" ", false);
|
||||
ERR_FAIL_COND_V(v.size() < 4, ERR_FILE_CORRUPT);
|
||||
|
||||
//not very fast, could be sped up
|
||||
|
||||
Vector<String> face[3];
|
||||
face[0] = v[1].split("/");
|
||||
face[1] = v[2].split("/");
|
||||
ERR_FAIL_COND_V(face[0].is_empty(), ERR_FILE_CORRUPT);
|
||||
|
||||
ERR_FAIL_COND_V(face[0].size() != face[1].size(), ERR_FILE_CORRUPT);
|
||||
for (int i = 2; i < v.size() - 1; i++) {
|
||||
face[2] = v[i + 1].split("/");
|
||||
|
||||
ERR_FAIL_COND_V(face[0].size() != face[2].size(), ERR_FILE_CORRUPT);
|
||||
for (int j = 0; j < 3; j++) {
|
||||
int idx = j;
|
||||
|
||||
if (idx < 2) {
|
||||
idx = 1 ^ idx;
|
||||
}
|
||||
|
||||
if (face[idx].size() == 3) {
|
||||
int norm = face[idx][2].to_int() - 1;
|
||||
if (norm < 0) {
|
||||
norm += normals.size() + 1;
|
||||
}
|
||||
ERR_FAIL_INDEX_V(norm, normals.size(), ERR_FILE_CORRUPT);
|
||||
surf_tool->set_normal(normals[norm]);
|
||||
if (generate_tangents && uvs.is_empty()) {
|
||||
// We can't generate tangents without UVs, so create dummy tangents.
|
||||
Vector3 tan = Vector3(normals[norm].z, -normals[norm].x, normals[norm].y).cross(normals[norm].normalized()).normalized();
|
||||
surf_tool->set_tangent(Plane(tan.x, tan.y, tan.z, 1.0));
|
||||
}
|
||||
} else {
|
||||
// No normals, use a dummy tangent since normals and tangents will be generated.
|
||||
if (generate_tangents && uvs.is_empty()) {
|
||||
// We can't generate tangents without UVs, so create dummy tangents.
|
||||
surf_tool->set_tangent(Plane(1.0, 0.0, 0.0, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
if (face[idx].size() >= 2 && !face[idx][1].is_empty()) {
|
||||
int uv = face[idx][1].to_int() - 1;
|
||||
if (uv < 0) {
|
||||
uv += uvs.size() + 1;
|
||||
}
|
||||
ERR_FAIL_INDEX_V(uv, uvs.size(), ERR_FILE_CORRUPT);
|
||||
surf_tool->set_uv(uvs[uv]);
|
||||
}
|
||||
|
||||
int vtx = face[idx][0].to_int() - 1;
|
||||
if (vtx < 0) {
|
||||
vtx += vertices.size() + 1;
|
||||
}
|
||||
ERR_FAIL_INDEX_V(vtx, vertices.size(), ERR_FILE_CORRUPT);
|
||||
|
||||
Vector3 vertex = vertices[vtx];
|
||||
if (!colors.is_empty()) {
|
||||
surf_tool->set_color(colors[vtx]);
|
||||
}
|
||||
surf_tool->set_smooth_group(smoothing ? smooth_group : no_smoothing_smooth_group);
|
||||
surf_tool->add_vertex(vertex);
|
||||
}
|
||||
|
||||
face[1] = face[2];
|
||||
}
|
||||
} else if (l.begins_with("s ")) { //smoothing
|
||||
String what = l.substr(2, l.length()).strip_edges();
|
||||
bool do_smooth;
|
||||
if (what == "off") {
|
||||
do_smooth = false;
|
||||
} else {
|
||||
do_smooth = true;
|
||||
}
|
||||
if (do_smooth != smoothing) {
|
||||
smoothing = do_smooth;
|
||||
if (smoothing) {
|
||||
smooth_group++;
|
||||
}
|
||||
}
|
||||
} else if (/*l.begins_with("g ") ||*/ l.begins_with("usemtl ") || (l.begins_with("o ") || f->eof_reached())) { //commit group to mesh
|
||||
uint64_t mesh_flags = RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
|
||||
|
||||
if (p_disable_compression) {
|
||||
mesh_flags = 0;
|
||||
} else {
|
||||
bool is_mesh_2d = true;
|
||||
|
||||
// Disable compression if all z equals 0 (the mesh is 2D).
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
if (!Math::is_zero_approx(vertices[i].z)) {
|
||||
is_mesh_2d = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_mesh_2d) {
|
||||
mesh_flags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//groups are too annoying
|
||||
if (surf_tool->get_vertex_array().size()) {
|
||||
//another group going on, commit it
|
||||
if (normals.size() == 0) {
|
||||
surf_tool->generate_normals();
|
||||
}
|
||||
|
||||
if (generate_tangents && uvs.size()) {
|
||||
surf_tool->generate_tangents();
|
||||
}
|
||||
|
||||
surf_tool->index();
|
||||
|
||||
print_verbose("OBJ: Current material library " + current_material_library + " has " + itos(material_map.has(current_material_library)));
|
||||
print_verbose("OBJ: Current material " + current_material + " has " + itos(material_map.has(current_material_library) && material_map[current_material_library].has(current_material)));
|
||||
Ref<StandardMaterial3D> material;
|
||||
if (material_map.has(current_material_library) && material_map[current_material_library].has(current_material)) {
|
||||
material = material_map[current_material_library][current_material];
|
||||
if (!colors.is_empty()) {
|
||||
material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
|
||||
}
|
||||
surf_tool->set_material(material);
|
||||
}
|
||||
|
||||
Array array = surf_tool->commit_to_arrays();
|
||||
|
||||
if (mesh_flags & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES && generate_tangents) {
|
||||
// Compression is enabled, so let's validate that the normals and tangents are correct.
|
||||
Vector<Vector3> norms = array[Mesh::ARRAY_NORMAL];
|
||||
Vector<float> tangents = array[Mesh::ARRAY_TANGENT];
|
||||
for (int vert = 0; vert < norms.size(); vert++) {
|
||||
Vector3 tan = Vector3(tangents[vert * 4 + 0], tangents[vert * 4 + 1], tangents[vert * 4 + 2]);
|
||||
if (abs(tan.dot(norms[vert])) > 0.0001) {
|
||||
// Tangent is not perpendicular to the normal, so we can't use compression.
|
||||
mesh_flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, array, TypedArray<Array>(), Dictionary(), material, name, mesh_flags);
|
||||
print_verbose("OBJ: Added surface :" + mesh->get_surface_name(mesh->get_surface_count() - 1));
|
||||
|
||||
if (!current_material.is_empty()) {
|
||||
if (mesh->get_surface_count() >= 1) {
|
||||
mesh->set_surface_name(mesh->get_surface_count() - 1, current_material.get_basename());
|
||||
}
|
||||
} else if (!current_group.is_empty()) {
|
||||
if (mesh->get_surface_count() >= 1) {
|
||||
mesh->set_surface_name(mesh->get_surface_count() - 1, current_group);
|
||||
}
|
||||
}
|
||||
|
||||
surf_tool->clear();
|
||||
surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
|
||||
}
|
||||
|
||||
if (l.begins_with("o ") || f->eof_reached()) {
|
||||
if (!p_single_mesh) {
|
||||
if (mesh->get_surface_count() > 0) {
|
||||
mesh->set_name(name);
|
||||
r_meshes.push_back(mesh);
|
||||
mesh.instantiate();
|
||||
}
|
||||
name = default_name;
|
||||
current_group = "";
|
||||
current_material = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (f->eof_reached()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (l.begins_with("o ")) {
|
||||
name = l.substr(2, l.length()).strip_edges();
|
||||
}
|
||||
|
||||
if (l.begins_with("usemtl ")) {
|
||||
current_material = l.replace("usemtl", "").strip_edges();
|
||||
}
|
||||
|
||||
if (l.begins_with("g ")) {
|
||||
current_group = l.substr(2, l.length()).strip_edges();
|
||||
}
|
||||
|
||||
} else if (l.begins_with("mtllib ")) { //parse material
|
||||
|
||||
current_material_library = l.replace("mtllib", "").strip_edges();
|
||||
if (!material_map.has(current_material_library)) {
|
||||
HashMap<String, Ref<StandardMaterial3D>> lib;
|
||||
String lib_path = current_material_library;
|
||||
if (lib_path.is_relative_path()) {
|
||||
lib_path = p_path.get_base_dir().path_join(current_material_library);
|
||||
}
|
||||
Error err = _parse_material_library(lib_path, lib, r_missing_deps);
|
||||
if (err == OK) {
|
||||
material_map[current_material_library] = lib;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p_single_mesh && mesh->get_surface_count() > 0) {
|
||||
r_meshes.push_back(mesh);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Node *EditorOBJImporter::import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) {
|
||||
List<Ref<ImporterMesh>> meshes;
|
||||
|
||||
Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, false, Vector3(1, 1, 1), Vector3(0, 0, 0), p_flags & IMPORT_FORCE_DISABLE_MESH_COMPRESSION, r_missing_deps);
|
||||
|
||||
if (err != OK) {
|
||||
if (r_err) {
|
||||
*r_err = err;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Node3D *scene = memnew(Node3D);
|
||||
|
||||
for (Ref<ImporterMesh> m : meshes) {
|
||||
ImporterMeshInstance3D *mi = memnew(ImporterMeshInstance3D);
|
||||
mi->set_mesh(m);
|
||||
mi->set_name(m->get_name());
|
||||
scene->add_child(mi, true);
|
||||
mi->set_owner(scene);
|
||||
}
|
||||
|
||||
if (r_err) {
|
||||
*r_err = OK;
|
||||
}
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
void EditorOBJImporter::get_extensions(List<String> *r_extensions) const {
|
||||
r_extensions->push_back("obj");
|
||||
}
|
||||
|
||||
EditorOBJImporter::EditorOBJImporter() {
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
String ResourceImporterOBJ::get_importer_name() const {
|
||||
return "wavefront_obj";
|
||||
}
|
||||
|
||||
String ResourceImporterOBJ::get_visible_name() const {
|
||||
return "OBJ as Mesh";
|
||||
}
|
||||
|
||||
void ResourceImporterOBJ::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
p_extensions->push_back("obj");
|
||||
}
|
||||
|
||||
String ResourceImporterOBJ::get_save_extension() const {
|
||||
return "mesh";
|
||||
}
|
||||
|
||||
String ResourceImporterOBJ::get_resource_type() const {
|
||||
return "Mesh";
|
||||
}
|
||||
|
||||
int ResourceImporterOBJ::get_format_version() const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ResourceImporterOBJ::get_preset_count() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String ResourceImporterOBJ::get_preset_name(int p_idx) const {
|
||||
return "";
|
||||
}
|
||||
|
||||
void ResourceImporterOBJ::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_tangents"), true));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "scale_mesh"), Vector3(1, 1, 1)));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "offset_mesh"), Vector3(0, 0, 0)));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "optimize_mesh"), true));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_disable_mesh_compression"), false));
|
||||
}
|
||||
|
||||
bool ResourceImporterOBJ::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Error ResourceImporterOBJ::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
|
||||
List<Ref<ImporterMesh>> meshes;
|
||||
|
||||
Error err = _parse_obj(p_source_file, meshes, true, p_options["generate_tangents"], p_options["optimize_mesh"], p_options["scale_mesh"], p_options["offset_mesh"], p_options["force_disable_mesh_compression"], nullptr);
|
||||
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
ERR_FAIL_COND_V(meshes.size() != 1, ERR_BUG);
|
||||
|
||||
String save_path = p_save_path + ".mesh";
|
||||
|
||||
err = ResourceSaver::save(meshes.front()->get()->get_mesh(), save_path);
|
||||
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save Mesh to file '" + save_path + "'.");
|
||||
|
||||
r_gen_files->push_back(save_path);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
ResourceImporterOBJ::ResourceImporterOBJ() {
|
||||
}
|
||||
72
engine/editor/import/3d/resource_importer_obj.h
Normal file
72
engine/editor/import/3d/resource_importer_obj.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/**************************************************************************/
|
||||
/* resource_importer_obj.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 RESOURCE_IMPORTER_OBJ_H
|
||||
#define RESOURCE_IMPORTER_OBJ_H
|
||||
|
||||
#include "resource_importer_scene.h"
|
||||
|
||||
class EditorOBJImporter : public EditorSceneFormatImporter {
|
||||
GDCLASS(EditorOBJImporter, 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;
|
||||
|
||||
EditorOBJImporter();
|
||||
};
|
||||
|
||||
class ResourceImporterOBJ : public ResourceImporter {
|
||||
GDCLASS(ResourceImporterOBJ, ResourceImporter);
|
||||
|
||||
public:
|
||||
virtual String get_importer_name() const override;
|
||||
virtual String get_visible_name() const override;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
|
||||
virtual String get_save_extension() const override;
|
||||
virtual String get_resource_type() const override;
|
||||
virtual int get_format_version() const override;
|
||||
|
||||
virtual int get_preset_count() const override;
|
||||
virtual String get_preset_name(int p_idx) const override;
|
||||
|
||||
virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const override;
|
||||
virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
|
||||
|
||||
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
|
||||
|
||||
// Threaded import can currently cause deadlocks, see GH-48265.
|
||||
virtual bool can_import_threaded() const override { return false; }
|
||||
|
||||
ResourceImporterOBJ();
|
||||
};
|
||||
|
||||
#endif // RESOURCE_IMPORTER_OBJ_H
|
||||
3272
engine/editor/import/3d/resource_importer_scene.cpp
Normal file
3272
engine/editor/import/3d/resource_importer_scene.cpp
Normal file
File diff suppressed because it is too large
Load diff
525
engine/editor/import/3d/resource_importer_scene.h
Normal file
525
engine/editor/import/3d/resource_importer_scene.h
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
/**************************************************************************/
|
||||
/* resource_importer_scene.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 RESOURCE_IMPORTER_SCENE_H
|
||||
#define RESOURCE_IMPORTER_SCENE_H
|
||||
|
||||
#include "core/error/error_macros.h"
|
||||
#include "core/io/resource_importer.h"
|
||||
#include "core/variant/dictionary.h"
|
||||
#include "scene/3d/importer_mesh_instance_3d.h"
|
||||
#include "scene/resources/3d/box_shape_3d.h"
|
||||
#include "scene/resources/3d/capsule_shape_3d.h"
|
||||
#include "scene/resources/3d/cylinder_shape_3d.h"
|
||||
#include "scene/resources/3d/importer_mesh.h"
|
||||
#include "scene/resources/3d/skin.h"
|
||||
#include "scene/resources/3d/sphere_shape_3d.h"
|
||||
#include "scene/resources/animation.h"
|
||||
#include "scene/resources/mesh.h"
|
||||
|
||||
class Material;
|
||||
class AnimationPlayer;
|
||||
|
||||
class ImporterMesh;
|
||||
class EditorSceneFormatImporter : public RefCounted {
|
||||
GDCLASS(EditorSceneFormatImporter, RefCounted);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
Node *import_scene_wrapper(const String &p_path, uint32_t p_flags, const Dictionary &p_options);
|
||||
Ref<Animation> import_animation_wrapper(const String &p_path, uint32_t p_flags, const Dictionary &p_options);
|
||||
|
||||
GDVIRTUAL0RC(uint32_t, _get_import_flags)
|
||||
GDVIRTUAL0RC(Vector<String>, _get_extensions)
|
||||
GDVIRTUAL3R(Object *, _import_scene, String, uint32_t, Dictionary)
|
||||
GDVIRTUAL1(_get_import_options, String)
|
||||
GDVIRTUAL3RC(Variant, _get_option_visibility, String, bool, String)
|
||||
|
||||
public:
|
||||
enum ImportFlags {
|
||||
IMPORT_SCENE = 1,
|
||||
IMPORT_ANIMATION = 2,
|
||||
IMPORT_FAIL_ON_MISSING_DEPENDENCIES = 4,
|
||||
IMPORT_GENERATE_TANGENT_ARRAYS = 8,
|
||||
IMPORT_USE_NAMED_SKIN_BINDS = 16,
|
||||
IMPORT_DISCARD_MESHES_AND_MATERIALS = 32, //used for optimizing animation import
|
||||
IMPORT_FORCE_DISABLE_MESH_COMPRESSION = 64,
|
||||
};
|
||||
|
||||
virtual uint32_t get_import_flags() const;
|
||||
virtual void get_extensions(List<String> *r_extensions) const;
|
||||
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);
|
||||
virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options);
|
||||
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options);
|
||||
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {}
|
||||
|
||||
EditorSceneFormatImporter() {}
|
||||
};
|
||||
|
||||
class EditorScenePostImport : public RefCounted {
|
||||
GDCLASS(EditorScenePostImport, RefCounted);
|
||||
|
||||
String source_file;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
GDVIRTUAL1R(Object *, _post_import, Node *)
|
||||
|
||||
public:
|
||||
String get_source_file() const;
|
||||
virtual Node *post_import(Node *p_scene);
|
||||
virtual void init(const String &p_source_file);
|
||||
EditorScenePostImport();
|
||||
};
|
||||
|
||||
class EditorScenePostImportPlugin : public RefCounted {
|
||||
GDCLASS(EditorScenePostImportPlugin, RefCounted);
|
||||
|
||||
public:
|
||||
enum InternalImportCategory {
|
||||
INTERNAL_IMPORT_CATEGORY_NODE,
|
||||
INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE,
|
||||
INTERNAL_IMPORT_CATEGORY_MESH,
|
||||
INTERNAL_IMPORT_CATEGORY_MATERIAL,
|
||||
INTERNAL_IMPORT_CATEGORY_ANIMATION,
|
||||
INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE,
|
||||
INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE,
|
||||
INTERNAL_IMPORT_CATEGORY_MAX
|
||||
};
|
||||
|
||||
private:
|
||||
mutable const HashMap<StringName, Variant> *current_options = nullptr;
|
||||
mutable const Dictionary *current_options_dict = nullptr;
|
||||
List<ResourceImporter::ImportOption> *current_option_list = nullptr;
|
||||
InternalImportCategory current_category = INTERNAL_IMPORT_CATEGORY_MAX;
|
||||
|
||||
protected:
|
||||
GDVIRTUAL1(_get_internal_import_options, int)
|
||||
GDVIRTUAL3RC(Variant, _get_internal_option_visibility, int, bool, String)
|
||||
GDVIRTUAL2RC(Variant, _get_internal_option_update_view_required, int, String)
|
||||
GDVIRTUAL4(_internal_process, int, Node *, Node *, Ref<Resource>)
|
||||
GDVIRTUAL1(_get_import_options, String)
|
||||
GDVIRTUAL3RC(Variant, _get_option_visibility, String, bool, String)
|
||||
GDVIRTUAL1(_pre_process, Node *)
|
||||
GDVIRTUAL1(_post_process, Node *)
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Variant get_option_value(const StringName &p_name) const;
|
||||
void add_import_option(const String &p_name, const Variant &p_default_value);
|
||||
void add_import_option_advanced(Variant::Type p_type, const String &p_name, const Variant &p_default_value, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = String(), int p_usage_flags = PROPERTY_USAGE_DEFAULT);
|
||||
|
||||
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options);
|
||||
virtual Variant get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
|
||||
virtual Variant get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
|
||||
|
||||
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options);
|
||||
|
||||
virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options);
|
||||
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
|
||||
|
||||
virtual void pre_process(Node *p_scene, const HashMap<StringName, Variant> &p_options);
|
||||
virtual void post_process(Node *p_scene, const HashMap<StringName, Variant> &p_options);
|
||||
|
||||
EditorScenePostImportPlugin() {}
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(EditorScenePostImportPlugin::InternalImportCategory)
|
||||
|
||||
class ResourceImporterScene : public ResourceImporter {
|
||||
GDCLASS(ResourceImporterScene, ResourceImporter);
|
||||
|
||||
static Vector<Ref<EditorSceneFormatImporter>> scene_importers;
|
||||
static Vector<Ref<EditorScenePostImportPlugin>> post_importer_plugins;
|
||||
|
||||
static ResourceImporterScene *scene_singleton;
|
||||
static ResourceImporterScene *animation_singleton;
|
||||
|
||||
enum LightBakeMode {
|
||||
LIGHT_BAKE_DISABLED,
|
||||
LIGHT_BAKE_STATIC,
|
||||
LIGHT_BAKE_STATIC_LIGHTMAPS,
|
||||
LIGHT_BAKE_DYNAMIC,
|
||||
};
|
||||
|
||||
enum MeshPhysicsMode {
|
||||
MESH_PHYSICS_DISABLED,
|
||||
MESH_PHYSICS_MESH_AND_STATIC_COLLIDER,
|
||||
MESH_PHYSICS_RIGID_BODY_AND_MESH,
|
||||
MESH_PHYSICS_STATIC_COLLIDER_ONLY,
|
||||
MESH_PHYSICS_AREA_ONLY,
|
||||
};
|
||||
|
||||
enum NavMeshMode {
|
||||
NAVMESH_DISABLED,
|
||||
NAVMESH_MESH_AND_NAVMESH,
|
||||
NAVMESH_NAVMESH_ONLY,
|
||||
};
|
||||
|
||||
enum OccluderMode {
|
||||
OCCLUDER_DISABLED,
|
||||
OCCLUDER_MESH_AND_OCCLUDER,
|
||||
OCCLUDER_OCCLUDER_ONLY,
|
||||
};
|
||||
|
||||
enum MeshOverride {
|
||||
MESH_OVERRIDE_DEFAULT,
|
||||
MESH_OVERRIDE_ENABLE,
|
||||
MESH_OVERRIDE_DISABLE,
|
||||
};
|
||||
|
||||
enum BodyType {
|
||||
BODY_TYPE_STATIC,
|
||||
BODY_TYPE_DYNAMIC,
|
||||
BODY_TYPE_AREA
|
||||
};
|
||||
|
||||
enum ShapeType {
|
||||
SHAPE_TYPE_DECOMPOSE_CONVEX,
|
||||
SHAPE_TYPE_SIMPLE_CONVEX,
|
||||
SHAPE_TYPE_TRIMESH,
|
||||
SHAPE_TYPE_BOX,
|
||||
SHAPE_TYPE_SPHERE,
|
||||
SHAPE_TYPE_CYLINDER,
|
||||
SHAPE_TYPE_CAPSULE,
|
||||
SHAPE_TYPE_AUTOMATIC,
|
||||
};
|
||||
|
||||
static Error _check_resource_save_paths(const Dictionary &p_data);
|
||||
Array _get_skinned_pose_transforms(ImporterMeshInstance3D *p_src_mesh_node);
|
||||
void _replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner);
|
||||
Node *_generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches);
|
||||
void _add_shapes(Node *p_node, const Vector<Ref<Shape3D>> &p_shapes);
|
||||
void _copy_meta(Object *p_src_object, Object *p_dst_object);
|
||||
|
||||
enum AnimationImportTracks {
|
||||
ANIMATION_IMPORT_TRACKS_IF_PRESENT,
|
||||
ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL,
|
||||
ANIMATION_IMPORT_TRACKS_NEVER,
|
||||
};
|
||||
enum TrackChannel {
|
||||
TRACK_CHANNEL_POSITION,
|
||||
TRACK_CHANNEL_ROTATION,
|
||||
TRACK_CHANNEL_SCALE,
|
||||
TRACK_CHANNEL_BLEND_SHAPE,
|
||||
TRACK_CHANNEL_MAX
|
||||
};
|
||||
|
||||
void _optimize_track_usage(AnimationPlayer *p_player, AnimationImportTracks *p_track_actions);
|
||||
|
||||
bool animation_importer = false;
|
||||
|
||||
public:
|
||||
static ResourceImporterScene *get_scene_singleton() { return scene_singleton; }
|
||||
static ResourceImporterScene *get_animation_singleton() { return animation_singleton; }
|
||||
|
||||
static void add_post_importer_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin, bool p_first_priority = false);
|
||||
static void remove_post_importer_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin);
|
||||
|
||||
const Vector<Ref<EditorSceneFormatImporter>> &get_scene_importers() const { return scene_importers; }
|
||||
static void add_scene_importer(Ref<EditorSceneFormatImporter> p_importer, bool p_first_priority = false);
|
||||
static void remove_scene_importer(Ref<EditorSceneFormatImporter> p_importer);
|
||||
static void get_scene_importer_extensions(List<String> *p_extensions);
|
||||
|
||||
static void clean_up_importer_plugins();
|
||||
|
||||
virtual String get_importer_name() const override;
|
||||
virtual String get_visible_name() const override;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
|
||||
virtual String get_save_extension() const override;
|
||||
virtual String get_resource_type() const override;
|
||||
virtual int get_format_version() const override;
|
||||
|
||||
virtual int get_preset_count() const override;
|
||||
virtual String get_preset_name(int p_idx) const override;
|
||||
|
||||
enum InternalImportCategory {
|
||||
INTERNAL_IMPORT_CATEGORY_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_NODE,
|
||||
INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE,
|
||||
INTERNAL_IMPORT_CATEGORY_MESH = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH,
|
||||
INTERNAL_IMPORT_CATEGORY_MATERIAL = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MATERIAL,
|
||||
INTERNAL_IMPORT_CATEGORY_ANIMATION = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION,
|
||||
INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE,
|
||||
INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE,
|
||||
INTERNAL_IMPORT_CATEGORY_MAX = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MAX
|
||||
};
|
||||
|
||||
void get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const;
|
||||
bool get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
|
||||
bool get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
|
||||
|
||||
virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const override;
|
||||
virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
|
||||
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
|
||||
// Import scenes *after* everything else (such as textures).
|
||||
virtual int get_import_order() const override { return ResourceImporter::IMPORT_ORDER_SCENE; }
|
||||
|
||||
void _pre_fix_global(Node *p_scene, const HashMap<StringName, Variant> &p_options) const;
|
||||
Node *_pre_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &r_collision_map, Pair<PackedVector3Array, PackedInt32Array> *r_occluder_arrays, List<Pair<NodePath, Node *>> &r_node_renames);
|
||||
Node *_pre_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps);
|
||||
Node *_post_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Pair<PackedVector3Array, PackedInt32Array> &r_occluder_arrays, HashSet<Ref<ImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps, float p_applied_root_scale);
|
||||
Node *_post_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps, bool p_remove_immutable_tracks);
|
||||
|
||||
Ref<Animation> _save_animation_to_file(Ref<Animation> anim, bool p_save_to_file, const String &p_save_to_path, bool p_keep_custom_tracks);
|
||||
void _create_slices(AnimationPlayer *ap, Ref<Animation> anim, const Array &p_clips, bool p_bake_all);
|
||||
void _optimize_animations(AnimationPlayer *anim, float p_max_vel_error, float p_max_ang_error, int p_prc_error);
|
||||
void _compress_animations(AnimationPlayer *anim, int p_page_size_kb);
|
||||
|
||||
Node *pre_import(const String &p_source_file, const HashMap<StringName, Variant> &p_options);
|
||||
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
|
||||
|
||||
virtual bool has_advanced_options() const override;
|
||||
virtual void show_advanced_options(const String &p_path) override;
|
||||
|
||||
virtual bool can_import_threaded() const override { return false; }
|
||||
|
||||
ResourceImporterScene(bool p_animation_import = false, bool p_singleton = false);
|
||||
~ResourceImporterScene();
|
||||
|
||||
template <typename M>
|
||||
static Vector<Ref<Shape3D>> get_collision_shapes(const Ref<ImporterMesh> &p_mesh, const M &p_options, float p_applied_root_scale);
|
||||
|
||||
template <typename M>
|
||||
static Transform3D get_collision_shapes_transform(const M &p_options);
|
||||
};
|
||||
|
||||
class EditorSceneFormatImporterESCN : public EditorSceneFormatImporter {
|
||||
GDCLASS(EditorSceneFormatImporterESCN, 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;
|
||||
};
|
||||
|
||||
template <typename M>
|
||||
Vector<Ref<Shape3D>> ResourceImporterScene::get_collision_shapes(const Ref<ImporterMesh> &p_mesh, const M &p_options, float p_applied_root_scale) {
|
||||
ERR_FAIL_COND_V(p_mesh.is_null(), Vector<Ref<Shape3D>>());
|
||||
|
||||
ShapeType generate_shape_type = SHAPE_TYPE_AUTOMATIC;
|
||||
if (p_options.has(SNAME("physics/shape_type"))) {
|
||||
generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int();
|
||||
}
|
||||
|
||||
if (generate_shape_type == SHAPE_TYPE_AUTOMATIC) {
|
||||
BodyType body_type = BODY_TYPE_STATIC;
|
||||
if (p_options.has(SNAME("physics/body_type"))) {
|
||||
body_type = (BodyType)p_options[SNAME("physics/body_type")].operator int();
|
||||
}
|
||||
|
||||
generate_shape_type = body_type == BODY_TYPE_DYNAMIC ? SHAPE_TYPE_DECOMPOSE_CONVEX : SHAPE_TYPE_TRIMESH;
|
||||
}
|
||||
|
||||
if (generate_shape_type == SHAPE_TYPE_DECOMPOSE_CONVEX) {
|
||||
Ref<MeshConvexDecompositionSettings> decomposition_settings = Ref<MeshConvexDecompositionSettings>();
|
||||
decomposition_settings.instantiate();
|
||||
bool advanced = false;
|
||||
if (p_options.has(SNAME("decomposition/advanced"))) {
|
||||
advanced = p_options[SNAME("decomposition/advanced")];
|
||||
}
|
||||
|
||||
if (advanced) {
|
||||
if (p_options.has(SNAME("decomposition/max_concavity"))) {
|
||||
decomposition_settings->set_max_concavity(p_options[SNAME("decomposition/max_concavity")]);
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/symmetry_planes_clipping_bias"))) {
|
||||
decomposition_settings->set_symmetry_planes_clipping_bias(p_options[SNAME("decomposition/symmetry_planes_clipping_bias")]);
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/revolution_axes_clipping_bias"))) {
|
||||
decomposition_settings->set_revolution_axes_clipping_bias(p_options[SNAME("decomposition/revolution_axes_clipping_bias")]);
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/min_volume_per_convex_hull"))) {
|
||||
decomposition_settings->set_min_volume_per_convex_hull(p_options[SNAME("decomposition/min_volume_per_convex_hull")]);
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/resolution"))) {
|
||||
decomposition_settings->set_resolution(p_options[SNAME("decomposition/resolution")]);
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/max_num_vertices_per_convex_hull"))) {
|
||||
decomposition_settings->set_max_num_vertices_per_convex_hull(p_options[SNAME("decomposition/max_num_vertices_per_convex_hull")]);
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/plane_downsampling"))) {
|
||||
decomposition_settings->set_plane_downsampling(p_options[SNAME("decomposition/plane_downsampling")]);
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/convexhull_downsampling"))) {
|
||||
decomposition_settings->set_convex_hull_downsampling(p_options[SNAME("decomposition/convexhull_downsampling")]);
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/normalize_mesh"))) {
|
||||
decomposition_settings->set_normalize_mesh(p_options[SNAME("decomposition/normalize_mesh")]);
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/mode"))) {
|
||||
decomposition_settings->set_mode((MeshConvexDecompositionSettings::Mode)p_options[SNAME("decomposition/mode")].operator int());
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/convexhull_approximation"))) {
|
||||
decomposition_settings->set_convex_hull_approximation(p_options[SNAME("decomposition/convexhull_approximation")]);
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/max_convex_hulls"))) {
|
||||
decomposition_settings->set_max_convex_hulls(MAX(1, (int)p_options[SNAME("decomposition/max_convex_hulls")]));
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("decomposition/project_hull_vertices"))) {
|
||||
decomposition_settings->set_project_hull_vertices(p_options[SNAME("decomposition/project_hull_vertices")]);
|
||||
}
|
||||
} else {
|
||||
int precision_level = 5;
|
||||
if (p_options.has(SNAME("decomposition/precision"))) {
|
||||
precision_level = p_options[SNAME("decomposition/precision")];
|
||||
}
|
||||
|
||||
const real_t precision = real_t(precision_level - 1) / 9.0;
|
||||
|
||||
decomposition_settings->set_max_concavity(Math::lerp(real_t(1.0), real_t(0.001), precision));
|
||||
decomposition_settings->set_min_volume_per_convex_hull(Math::lerp(real_t(0.01), real_t(0.0001), precision));
|
||||
decomposition_settings->set_resolution(Math::lerp(10'000, 100'000, precision));
|
||||
decomposition_settings->set_max_num_vertices_per_convex_hull(Math::lerp(32, 64, precision));
|
||||
decomposition_settings->set_plane_downsampling(Math::lerp(3, 16, precision));
|
||||
decomposition_settings->set_convex_hull_downsampling(Math::lerp(3, 16, precision));
|
||||
decomposition_settings->set_max_convex_hulls(Math::lerp(1, 32, precision));
|
||||
}
|
||||
|
||||
return p_mesh->convex_decompose(decomposition_settings);
|
||||
} else if (generate_shape_type == SHAPE_TYPE_SIMPLE_CONVEX) {
|
||||
Vector<Ref<Shape3D>> shapes;
|
||||
shapes.push_back(p_mesh->create_convex_shape(true, /*Passing false, otherwise VHACD will be used to simplify (Decompose) the Mesh.*/ false));
|
||||
return shapes;
|
||||
} else if (generate_shape_type == SHAPE_TYPE_TRIMESH) {
|
||||
Vector<Ref<Shape3D>> shapes;
|
||||
shapes.push_back(p_mesh->create_trimesh_shape());
|
||||
return shapes;
|
||||
} else if (generate_shape_type == SHAPE_TYPE_BOX) {
|
||||
Ref<BoxShape3D> box;
|
||||
box.instantiate();
|
||||
if (p_options.has(SNAME("primitive/size"))) {
|
||||
box->set_size(p_options[SNAME("primitive/size")].operator Vector3() * p_applied_root_scale);
|
||||
} else {
|
||||
box->set_size(Vector3(2, 2, 2) * p_applied_root_scale);
|
||||
}
|
||||
|
||||
Vector<Ref<Shape3D>> shapes;
|
||||
shapes.push_back(box);
|
||||
return shapes;
|
||||
|
||||
} else if (generate_shape_type == SHAPE_TYPE_SPHERE) {
|
||||
Ref<SphereShape3D> sphere;
|
||||
sphere.instantiate();
|
||||
if (p_options.has(SNAME("primitive/radius"))) {
|
||||
sphere->set_radius(p_options[SNAME("primitive/radius")].operator float() * p_applied_root_scale);
|
||||
} else {
|
||||
sphere->set_radius(1.0f * p_applied_root_scale);
|
||||
}
|
||||
|
||||
Vector<Ref<Shape3D>> shapes;
|
||||
shapes.push_back(sphere);
|
||||
return shapes;
|
||||
} else if (generate_shape_type == SHAPE_TYPE_CYLINDER) {
|
||||
Ref<CylinderShape3D> cylinder;
|
||||
cylinder.instantiate();
|
||||
if (p_options.has(SNAME("primitive/height"))) {
|
||||
cylinder->set_height(p_options[SNAME("primitive/height")].operator float() * p_applied_root_scale);
|
||||
} else {
|
||||
cylinder->set_height(1.0f * p_applied_root_scale);
|
||||
}
|
||||
if (p_options.has(SNAME("primitive/radius"))) {
|
||||
cylinder->set_radius(p_options[SNAME("primitive/radius")].operator float() * p_applied_root_scale);
|
||||
} else {
|
||||
cylinder->set_radius(1.0f * p_applied_root_scale);
|
||||
}
|
||||
|
||||
Vector<Ref<Shape3D>> shapes;
|
||||
shapes.push_back(cylinder);
|
||||
return shapes;
|
||||
} else if (generate_shape_type == SHAPE_TYPE_CAPSULE) {
|
||||
Ref<CapsuleShape3D> capsule;
|
||||
capsule.instantiate();
|
||||
if (p_options.has(SNAME("primitive/height"))) {
|
||||
capsule->set_height(p_options[SNAME("primitive/height")].operator float() * p_applied_root_scale);
|
||||
} else {
|
||||
capsule->set_height(1.0f * p_applied_root_scale);
|
||||
}
|
||||
if (p_options.has(SNAME("primitive/radius"))) {
|
||||
capsule->set_radius(p_options[SNAME("primitive/radius")].operator float() * p_applied_root_scale);
|
||||
} else {
|
||||
capsule->set_radius(1.0f * p_applied_root_scale);
|
||||
}
|
||||
|
||||
Vector<Ref<Shape3D>> shapes;
|
||||
shapes.push_back(capsule);
|
||||
return shapes;
|
||||
}
|
||||
return Vector<Ref<Shape3D>>();
|
||||
}
|
||||
|
||||
template <typename M>
|
||||
Transform3D ResourceImporterScene::get_collision_shapes_transform(const M &p_options) {
|
||||
Transform3D transform;
|
||||
|
||||
ShapeType generate_shape_type = SHAPE_TYPE_AUTOMATIC;
|
||||
if (p_options.has(SNAME("physics/shape_type"))) {
|
||||
generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int();
|
||||
}
|
||||
|
||||
if (generate_shape_type == SHAPE_TYPE_AUTOMATIC) {
|
||||
BodyType body_type = BODY_TYPE_STATIC;
|
||||
if (p_options.has(SNAME("physics/body_type"))) {
|
||||
body_type = (BodyType)p_options[SNAME("physics/body_type")].operator int();
|
||||
}
|
||||
|
||||
generate_shape_type = body_type == BODY_TYPE_DYNAMIC ? SHAPE_TYPE_DECOMPOSE_CONVEX : SHAPE_TYPE_TRIMESH;
|
||||
}
|
||||
|
||||
if (generate_shape_type == SHAPE_TYPE_BOX ||
|
||||
generate_shape_type == SHAPE_TYPE_SPHERE ||
|
||||
generate_shape_type == SHAPE_TYPE_CYLINDER ||
|
||||
generate_shape_type == SHAPE_TYPE_CAPSULE) {
|
||||
if (p_options.has(SNAME("primitive/position"))) {
|
||||
transform.origin = p_options[SNAME("primitive/position")];
|
||||
}
|
||||
|
||||
if (p_options.has(SNAME("primitive/rotation"))) {
|
||||
transform.basis = Basis::from_euler(p_options[SNAME("primitive/rotation")].operator Vector3() * (Math_PI / 180.0));
|
||||
}
|
||||
}
|
||||
return transform;
|
||||
}
|
||||
|
||||
#endif // RESOURCE_IMPORTER_SCENE_H
|
||||
1856
engine/editor/import/3d/scene_import_settings.cpp
Normal file
1856
engine/editor/import/3d/scene_import_settings.cpp
Normal file
File diff suppressed because it is too large
Load diff
253
engine/editor/import/3d/scene_import_settings.h
Normal file
253
engine/editor/import/3d/scene_import_settings.h
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/**************************************************************************/
|
||||
/* scene_import_settings.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 SCENE_IMPORT_SETTINGS_H
|
||||
#define SCENE_IMPORT_SETTINGS_H
|
||||
|
||||
#include "editor/import/3d/resource_importer_scene.h"
|
||||
#include "scene/3d/camera_3d.h"
|
||||
#include "scene/3d/light_3d.h"
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/item_list.h"
|
||||
#include "scene/gui/menu_button.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/gui/slider.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/subviewport_container.h"
|
||||
#include "scene/gui/tab_container.h"
|
||||
#include "scene/gui/tree.h"
|
||||
#include "scene/resources/3d/primitive_meshes.h"
|
||||
#include "scene/resources/3d/sky_material.h"
|
||||
|
||||
class EditorFileDialog;
|
||||
class EditorInspector;
|
||||
class SceneImportSettingsData;
|
||||
|
||||
class SceneImportSettingsDialog : public ConfirmationDialog {
|
||||
GDCLASS(SceneImportSettingsDialog, ConfirmationDialog)
|
||||
|
||||
static SceneImportSettingsDialog *singleton;
|
||||
|
||||
enum Actions {
|
||||
ACTION_EXTRACT_MATERIALS,
|
||||
ACTION_CHOOSE_MESH_SAVE_PATHS,
|
||||
ACTION_CHOOSE_ANIMATION_SAVE_PATHS,
|
||||
};
|
||||
|
||||
Node *scene = nullptr;
|
||||
|
||||
HSplitContainer *tree_split = nullptr;
|
||||
HSplitContainer *property_split = nullptr;
|
||||
TabContainer *data_mode = nullptr;
|
||||
Tree *scene_tree = nullptr;
|
||||
Tree *mesh_tree = nullptr;
|
||||
Tree *material_tree = nullptr;
|
||||
|
||||
EditorInspector *inspector = nullptr;
|
||||
|
||||
SubViewport *base_viewport = nullptr;
|
||||
|
||||
Camera3D *camera = nullptr;
|
||||
Ref<CameraAttributesPractical> camera_attributes;
|
||||
Ref<Environment> environment;
|
||||
Ref<Sky> sky;
|
||||
Ref<ProceduralSkyMaterial> procedural_sky_material;
|
||||
bool first_aabb = false;
|
||||
AABB contents_aabb;
|
||||
|
||||
Button *light_1_switch = nullptr;
|
||||
Button *light_2_switch = nullptr;
|
||||
Button *light_rotate_switch = nullptr;
|
||||
|
||||
struct ThemeCache {
|
||||
Ref<Texture2D> light_1_icon;
|
||||
Ref<Texture2D> light_2_icon;
|
||||
Ref<Texture2D> rotate_icon;
|
||||
} theme_cache;
|
||||
|
||||
DirectionalLight3D *light1 = nullptr;
|
||||
DirectionalLight3D *light2 = nullptr;
|
||||
Ref<ArrayMesh> selection_mesh;
|
||||
MeshInstance3D *node_selected = nullptr;
|
||||
|
||||
MeshInstance3D *mesh_preview = nullptr;
|
||||
Ref<SphereMesh> material_preview;
|
||||
|
||||
AnimationPlayer *animation_player = nullptr;
|
||||
List<Skeleton3D *> skeletons;
|
||||
PanelContainer *animation_preview = nullptr;
|
||||
HSlider *animation_slider = nullptr;
|
||||
Button *animation_play_button = nullptr;
|
||||
Button *animation_stop_button = nullptr;
|
||||
Animation::LoopMode animation_loop_mode = Animation::LOOP_NONE;
|
||||
bool animation_pingpong = false;
|
||||
bool previous_import_as_skeleton = false;
|
||||
bool previous_rest_as_reset = false;
|
||||
|
||||
Ref<StandardMaterial3D> collider_mat;
|
||||
|
||||
float cam_rot_x = 0.0f;
|
||||
float cam_rot_y = 0.0f;
|
||||
float cam_zoom = 0.0f;
|
||||
|
||||
void _update_scene();
|
||||
|
||||
struct MaterialData {
|
||||
bool has_import_id;
|
||||
Ref<Material> material;
|
||||
TreeItem *scene_node = nullptr;
|
||||
TreeItem *mesh_node = nullptr;
|
||||
TreeItem *material_node = nullptr;
|
||||
|
||||
float cam_rot_x = -Math_PI / 4;
|
||||
float cam_rot_y = -Math_PI / 4;
|
||||
float cam_zoom = 1;
|
||||
|
||||
HashMap<StringName, Variant> settings;
|
||||
};
|
||||
HashMap<String, MaterialData> material_map;
|
||||
HashMap<Ref<Material>, String> unnamed_material_name_map;
|
||||
|
||||
struct MeshData {
|
||||
bool has_import_id;
|
||||
Ref<Mesh> mesh;
|
||||
TreeItem *scene_node = nullptr;
|
||||
TreeItem *mesh_node = nullptr;
|
||||
|
||||
float cam_rot_x = -Math_PI / 4;
|
||||
float cam_rot_y = -Math_PI / 4;
|
||||
float cam_zoom = 1;
|
||||
HashMap<StringName, Variant> settings;
|
||||
};
|
||||
HashMap<String, MeshData> mesh_map;
|
||||
|
||||
struct AnimationData {
|
||||
Ref<Animation> animation;
|
||||
TreeItem *scene_node = nullptr;
|
||||
HashMap<StringName, Variant> settings;
|
||||
};
|
||||
HashMap<String, AnimationData> animation_map;
|
||||
|
||||
struct NodeData {
|
||||
Node *node = nullptr;
|
||||
TreeItem *scene_node = nullptr;
|
||||
HashMap<StringName, Variant> settings;
|
||||
};
|
||||
HashMap<String, NodeData> node_map;
|
||||
|
||||
void _fill_material(Tree *p_tree, const Ref<Material> &p_material, TreeItem *p_parent);
|
||||
void _fill_mesh(Tree *p_tree, const Ref<Mesh> &p_mesh, TreeItem *p_parent);
|
||||
void _fill_animation(Tree *p_tree, const Ref<Animation> &p_anim, const String &p_name, TreeItem *p_parent);
|
||||
void _fill_scene(Node *p_node, TreeItem *p_parent_item);
|
||||
|
||||
HashSet<Ref<Mesh>> mesh_set;
|
||||
|
||||
String selected_type;
|
||||
String selected_id;
|
||||
|
||||
bool selecting = false;
|
||||
|
||||
void _update_view_gizmos();
|
||||
void _update_camera();
|
||||
void _select(Tree *p_from, const String &p_type, const String &p_id);
|
||||
void _inspector_property_edited(const String &p_name);
|
||||
void _reset_bone_transforms();
|
||||
void _play_animation();
|
||||
void _stop_current_animation();
|
||||
void _reset_animation(const String &p_animation_name = "");
|
||||
void _animation_slider_value_changed(double p_value);
|
||||
void _animation_finished(const StringName &p_name);
|
||||
void _material_tree_selected();
|
||||
void _mesh_tree_selected();
|
||||
void _scene_tree_selected();
|
||||
void _cleanup();
|
||||
void _on_light_1_switch_pressed();
|
||||
void _on_light_2_switch_pressed();
|
||||
void _on_light_rotate_switch_pressed();
|
||||
|
||||
void _viewport_input(const Ref<InputEvent> &p_input);
|
||||
|
||||
HashMap<StringName, Variant> defaults;
|
||||
|
||||
SceneImportSettingsData *scene_import_settings_data = nullptr;
|
||||
|
||||
void _re_import();
|
||||
|
||||
String base_path;
|
||||
|
||||
MenuButton *action_menu = nullptr;
|
||||
|
||||
ConfirmationDialog *external_paths = nullptr;
|
||||
Tree *external_path_tree = nullptr;
|
||||
EditorFileDialog *save_path = nullptr;
|
||||
OptionButton *external_extension_type = nullptr;
|
||||
|
||||
EditorFileDialog *item_save_path = nullptr;
|
||||
|
||||
void _menu_callback(int p_id);
|
||||
void _save_dir_callback(const String &p_path);
|
||||
|
||||
int current_action = 0;
|
||||
|
||||
Vector<TreeItem *> save_path_items;
|
||||
|
||||
TreeItem *save_path_item = nullptr;
|
||||
void _save_path_changed(const String &p_path);
|
||||
void _browse_save_callback(Object *p_item, int p_column, int p_id, MouseButton p_button);
|
||||
void _save_dir_confirm();
|
||||
|
||||
Dictionary base_subresource_settings;
|
||||
|
||||
void _load_default_subresource_settings(HashMap<StringName, Variant> &settings, const String &p_type, const String &p_import_id, ResourceImporterScene::InternalImportCategory p_category);
|
||||
|
||||
bool editing_animation = false;
|
||||
bool generate_collider = false;
|
||||
|
||||
Timer *update_view_timer = nullptr;
|
||||
|
||||
protected:
|
||||
virtual void _update_theme_item_cache() override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
bool is_editing_animation() const { return editing_animation; }
|
||||
void request_generate_collider();
|
||||
void update_view();
|
||||
void open_settings(const String &p_path, bool p_for_animation = false);
|
||||
static SceneImportSettingsDialog *get_singleton();
|
||||
Node *get_selected_node();
|
||||
SceneImportSettingsDialog();
|
||||
~SceneImportSettingsDialog();
|
||||
};
|
||||
|
||||
#endif // SCENE_IMPORT_SETTINGS_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue