initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

2380
editor/import/3d/collada.cpp Normal file

File diff suppressed because it is too large Load Diff

546
editor/import/3d/collada.h Normal file
View File

@@ -0,0 +1,546 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "core/io/xml_parser.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 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;
};
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;
};
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;
};
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;
};
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;
};
struct MorphControllerData {
String mesh;
String mode;
struct Source {
int stride = 1;
Vector<String> sarray; //maybe for names
Vector<float> array;
};
HashMap<String, Source> sources;
HashMap<String, String> targets;
};
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;
}
}
};
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;
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;
};
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;
};
Vector<float> get_value_at_time(float p_time) const;
Vector<Key> keys;
};
/****************/
/* 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;
Error load(const String &p_path, int p_flags = 0);
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();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "editor/import/3d/resource_importer_scene.h"
class EditorSceneFormatImporterCollada : public EditorSceneFormatImporter {
GDCLASS(EditorSceneFormatImporterCollada, EditorSceneFormatImporter);
public:
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;
};

View File

@@ -0,0 +1,247 @@
/**************************************************************************/
/* 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 "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", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 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 + "/" + String(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 + "/" + String(node->get_path_to(orig_node)));
}
}
break;
}
node = node->get_parent();
}
}
}
}
skeleton->set_name(unique_name);
skeleton->set_unique_name_in_owner(true);
}
}
}

View File

@@ -0,0 +1,43 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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);
};

View File

@@ -0,0 +1,958 @@
/**************************************************************************/
/* 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 "scene/3d/bone_attachment_3d.h"
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/3d/retarget_modifier_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::INT, "retarget/rest_fixer/retarget_method", PROPERTY_HINT_ENUM, "None,Overwrite Axis,Use Retarget Modifier", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/keep_global_rest_on_leftovers"), true));
String skeleton_bones_must_be_renamed_warning = String(
"The skeleton modifier option uses SkeletonProfile as a list of bone names and retargets by name matching. Without renaming, retargeting by modifier will not work and the track path of the animation will be broken and it will be not playbacked correctly."); // TODO: translate.
r_options->push_back(ResourceImporter::ImportOption(
PropertyInfo(
Variant::STRING, U"retarget/rest_fixer/\u26A0_validation_warning/skeleton_bones_must_be_renamed",
PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY),
Variant(skeleton_bones_must_be_renamed_warning)));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/use_global_pose"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::STRING, "retarget/rest_fixer/original_skeleton_name"), "OriginalSkeleton"));
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, vformat("%s:", Variant::STRING_NAME)), 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, const String &p_scene_import_type, 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 int(p_options["retarget/rest_fixer/retarget_method"]) == 1;
} else if (p_option == "retarget/rest_fixer/original_skeleton_name" || p_option == "retarget/rest_fixer/use_global_pose") {
return int(p_options["retarget/rest_fixer/retarget_method"]) == 2;
} else if (p_option.begins_with("retarget/") && p_option.ends_with("skeleton_bones_must_be_renamed")) {
return int(p_options["retarget/rest_fixer/retarget_method"]) == 2 && bool(p_options["retarget/bone_renamer/rename_bones"]) == false;
}
}
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_null()) {
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 by changing node transform.
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) {
if (String(name).contains_char('/')) {
continue; // Avoid animation library which may be created by importer dynamically.
}
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;
if (Math::is_zero_approx(prof_dir.length_squared()) || Math::is_zero_approx(src_dir.length_squared())) {
continue;
}
prof_dir.normalize();
src_dir.normalize();
// Rotate rest.
if (Math::abs(Math::rad_to_deg(src_dir.angle_to(prof_dir))) > float(p_options["retarget/rest_fixer/fix_silhouette/threshold"])) {
Basis diff_b = Basis(Quaternion(src_dir, prof_dir));
// 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 = std::abs(src_skeleton->get_bone_global_rest(src_bone_idx).origin.y);
if (motion_scale > 0) {
src_skeleton->set_motion_scale(motion_scale);
}
}
}
bool is_using_modifier = int(p_options["retarget/rest_fixer/retarget_method"]) == 2;
bool is_using_global_pose = bool(p_options["retarget/rest_fixer/use_global_pose"]);
Skeleton3D *orig_skeleton = nullptr;
Skeleton3D *profile_skeleton = nullptr;
// Retarget in some way.
if (int(p_options["retarget/rest_fixer/retarget_method"]) > 0) {
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));
}
// Build structure for modifier.
if (is_using_modifier) {
orig_skeleton = src_skeleton;
// Duplicate src_skeleton to modify animation tracks, it will memdelele after that animation track modification.
src_skeleton = memnew(Skeleton3D);
for (int i = 0; i < orig_skeleton->get_bone_count(); i++) {
src_skeleton->add_bone(orig_skeleton->get_bone_name(i));
src_skeleton->set_bone_rest(i, orig_skeleton->get_bone_rest(i));
src_skeleton->set_bone_pose(i, orig_skeleton->get_bone_pose(i));
}
for (int i = 0; i < orig_skeleton->get_bone_count(); i++) {
src_skeleton->set_bone_parent(i, orig_skeleton->get_bone_parent(i));
}
src_skeleton->set_motion_scale(orig_skeleton->get_motion_scale());
// Rename orig_skeleton (previous src_skeleton), since it is not animated by animation track with GeneralSkeleton.
String original_skeleton_name = String(p_options["retarget/rest_fixer/original_skeleton_name"]);
String skel_name = orig_skeleton->get_name();
ERR_FAIL_COND_MSG(original_skeleton_name.is_empty(), "Original skeleton name cannot be empty.");
ERR_FAIL_COND_MSG(original_skeleton_name == skel_name, "Original skeleton name must be different from unique skeleton name.");
// Rename profile skeleton to be general skeleton.
profile_skeleton = memnew(Skeleton3D);
bool is_unique = orig_skeleton->is_unique_name_in_owner();
if (is_unique) {
orig_skeleton->set_unique_name_in_owner(false);
}
orig_skeleton->set_name(original_skeleton_name);
profile_skeleton->set_name(skel_name);
if (is_unique) {
profile_skeleton->set_unique_name_in_owner(true);
}
// Build profile skeleton bones.
int len = profile->get_bone_size();
for (int i = 0; i < len; i++) {
profile_skeleton->add_bone(profile->get_bone_name(i));
profile_skeleton->set_bone_rest(i, profile->get_reference_pose(i));
}
for (int i = 0; i < len; i++) {
int target_parent = profile_skeleton->find_bone(profile->get_bone_parent(i));
if (target_parent >= 0) {
profile_skeleton->set_bone_parent(i, target_parent);
}
}
for (int i = 0; i < len; i++) {
Vector3 origin;
int found = orig_skeleton->find_bone(profile->get_bone_name(i));
String parent_name = profile->get_bone_parent(i);
if (found >= 0) {
origin = orig_skeleton->get_bone_global_rest(found).origin;
if (profile->get_bone_name(i) != profile->get_root_bone()) {
int src_parent = -1;
while (src_parent < 0 && !parent_name.is_empty()) {
src_parent = orig_skeleton->find_bone(parent_name);
parent_name = profile->get_bone_parent(profile->find_bone(parent_name));
}
if (src_parent >= 0) {
Transform3D parent_grest = orig_skeleton->get_bone_global_rest(src_parent);
origin = origin - parent_grest.origin;
}
}
}
int target_parent = profile_skeleton->find_bone(profile->get_bone_parent(i));
if (target_parent >= 0) {
origin = profile_skeleton->get_bone_global_rest(target_parent).basis.get_rotation_quaternion().xform_inv(origin);
}
profile_skeleton->set_bone_rest(i, Transform3D(profile_skeleton->get_bone_rest(i).basis, origin));
}
profile_skeleton->set_motion_scale(orig_skeleton->get_motion_scale());
profile_skeleton->reset_bone_poses();
// Make structure with modifier.
Node *owner = p_node->get_owner();
Node *pr = orig_skeleton->get_parent();
pr->add_child(profile_skeleton);
profile_skeleton->set_owner(owner);
RetargetModifier3D *mod = memnew(RetargetModifier3D);
profile_skeleton->add_child(mod);
mod->set_owner(owner);
mod->set_name("RetargetModifier3D");
orig_skeleton->set_owner(nullptr);
orig_skeleton->reparent(mod, false);
orig_skeleton->set_owner(owner);
orig_skeleton->set_unique_name_in_owner(true);
mod->set_use_global_pose(is_using_global_pose);
mod->set_profile(profile);
// Fix skeleton name in animation.
// Mapped skeleton is animated by %GenerarSkeleton:RenamedBoneName.
// Unmapped skeleton is animated by %OriginalSkeleton:OriginalBoneName.
if (is_using_modifier) {
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
String general_skeleton_pathname = UNIQUE_NODE_PREFIX + profile_skeleton->get_name();
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_name_count() == 0) {
return;
}
if (anim->track_get_path(i).get_name(0) == general_skeleton_pathname) {
bool replace = false;
if (anim->track_get_path(i).get_subname_count() > 0) {
int found = profile_skeleton->find_bone(anim->track_get_path(i).get_concatenated_subnames());
if (found < 0) {
replace = true;
}
} else {
replace = true;
}
if (replace) {
String path_string = UNIQUE_NODE_PREFIX + original_skeleton_name;
if (anim->track_get_path(i).get_name_count() > 1) {
Vector<StringName> names = anim->track_get_path(i).get_names();
names.remove_at(0);
for (int j = 0; j < names.size(); j++) {
path_string += "/" + names[i].operator String();
}
}
if (anim->track_get_path(i).get_subname_count() > 0) {
path_string = path_string + String(":") + anim->track_get_path(i).get_concatenated_subnames();
}
anim->track_set_path(i, path_string);
}
}
}
}
}
}
}
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.
// When both is_using_modifier and is_using_global_pose are enabled, this array is used for detecting warning.
Vector<int> keep_bone_rest;
if (is_using_modifier || 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> descendants_to_process = src_skeleton->get_bone_children(src_idx);
while (descendants_to_process.size() > 0) {
int desc_idx = descendants_to_process[0];
descendants_to_process.erase(desc_idx);
Vector<int> desc_children = src_skeleton->get_bone_children(desc_idx);
for (const int &desc_child : desc_children) {
descendants_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) {
// Mapped bone uses reference pose.
// It is fine to change rest here even though is_using_modifier is enabled, since next process is aborted with unmapped bones.
tgt_rot = src_pg.inverse() * prof_skeleton->get_bone_global_rest(prof_idx).basis;
} else if (keep_global_rest_leftovers && keep_bone_rest.has(src_idx)) {
// Non-Mapped bones without mapped children keeps global rest.
tgt_rot = src_pg.inverse() * old_skeleton_global_rest[src_idx].basis;
}
}
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 by changing rest.
bool warning_detected = false;
{
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 ||
(is_using_modifier && track_skeleton != profile_skeleton && track_skeleton != orig_skeleton) ||
(!is_using_modifier && 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);
if (is_using_modifier) {
int prof_idx = profile->find_bone(bn);
if (prof_idx < 0) {
if (keep_bone_rest.has(bone_idx)) {
warning_detected = true;
}
continue; // If is_using_modifier, the original skeleton rest is not changed.
}
}
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 (is_using_global_pose && warning_detected) {
// TODO:
// Theoretically, if A and its conversion are calculated correctly taking into account the difference in the number of bones,
// there is no need to disable use_global_pose, but this is probably a fairly niche case.
WARN_PRINT_ED("Animated extra bone between mapped bones detected, consider disabling Use Global Pose option to prevent that the pose origin be overridden by the RetargetModifier3D.");
}
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);
}
}
if (is_using_modifier) {
memdelete(src_skeleton);
src_skeleton = profile_skeleton;
}
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 ||
(is_using_modifier && track_skeleton != profile_skeleton && track_skeleton != orig_skeleton) ||
(!is_using_modifier && 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_using_modifier && 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());
}
if (orig_skeleton) {
for (int i = 0; i < orig_skeleton->get_bone_count(); i++) {
Transform3D fixed_rest = orig_skeleton->get_bone_rest(i);
orig_skeleton->set_bone_pose_position(i, fixed_rest.origin);
orig_skeleton->set_bone_pose_rotation(i, fixed_rest.basis.get_rotation_quaternion());
orig_skeleton->set_bone_pose_scale(i, fixed_rest.basis.get_scale());
}
}
}
}
memdelete(prof_skeleton);
}
}

View File

@@ -0,0 +1,42 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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, const String &p_scene_import_type, 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;
};

View File

@@ -0,0 +1,155 @@
/**************************************************************************/
/* 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 "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::INT, "retarget/remove_tracks/unmapped_bones", PROPERTY_HINT_ENUM, "None,Remove,Separate Library"), 0));
}
}
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_null()) {
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"]);
int separate_unmapped_bones = int(p_options["retarget/remove_tracks/unmapped_bones"]);
if (!remove_positions && separate_unmapped_bones == 0) {
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);
Ref<AnimationLibrary> unmapped_al;
unmapped_al.instantiate();
for (const StringName &name : anims) {
Ref<Animation> anim = ap->get_animation(name);
int track_len = anim->get_track_count();
Vector<int> remove_indices;
Vector<int> mapped_bone_indices;
Vector<int> unmapped_bone_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 (prof_idx < 0) {
unmapped_bone_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()) {
mapped_bone_indices.push_back(i);
continue;
}
remove_indices.push_back(i);
} else {
mapped_bone_indices.push_back(i);
}
}
}
if (remove_except_bone) {
remove_indices.push_back(i);
}
}
if (separate_unmapped_bones == 2 && !unmapped_bone_indices.is_empty()) {
Ref<Animation> unmapped_anim = anim->duplicate();
Vector<int> to_delete;
to_delete.append_array(mapped_bone_indices);
to_delete.append_array(remove_indices);
to_delete.sort();
to_delete.reverse();
for (int E : to_delete) {
unmapped_anim->remove_track(E);
}
unmapped_al->add_animation(name, unmapped_anim);
}
if (separate_unmapped_bones >= 1) {
remove_indices.append_array(unmapped_bone_indices);
remove_indices.sort();
}
remove_indices.reverse();
for (int i = 0; i < remove_indices.size(); i++) {
anim->remove_track(remove_indices[i]);
}
}
if (unmapped_al->get_animation_list_size() == 0) {
unmapped_al.unref();
} else if (separate_unmapped_bones == 2) {
ap->add_animation_library("unmapped_bones", unmapped_al);
}
}
}
}

View File

@@ -0,0 +1,41 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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;
};

View File

@@ -0,0 +1,681 @@
/**************************************************************************/
/* 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/node_3d.h"
#include "scene/resources/3d/importer_mesh.h"
#include "scene/resources/mesh.h"
#include "scene/resources/surface_tool.h"
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_char('\\', '/').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_char('\\', '/').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_char('\\', '/').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_char('\\', '/').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_generate_lods, bool p_generate_shadow_mesh, bool p_generate_lightmap_uv2, float p_generate_lightmap_uv2_texel_size, const PackedByteArray &p_src_lightmap_cache, Vector3 p_scale_mesh, Vector3 p_offset_mesh, bool p_disable_compression, Vector<Vector<uint8_t>> &r_lightmap_caches, 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;
bool uses_uvs = false;
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;
}
// Check UVs before faces as we may need to generate dummy tangents if there are no UVs.
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]);
uses_uvs = true;
}
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 && !uses_uvs) {
// 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 && !uses_uvs) {
// We can't generate tangents without UVs, so create dummy tangents.
surf_tool->set_tangent(Plane(1.0, 0.0, 0.0, 1.0));
}
}
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).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.is_empty()) {
surf_tool->generate_normals();
}
if (generate_tangents && uses_uvs) {
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 && uses_uvs) {
// Compression is enabled, so let's validate that the normals and generated tangents are correct.
Vector<Vector3> norms = array[Mesh::ARRAY_NORMAL];
Vector<float> tangents = array[Mesh::ARRAY_TANGENT];
ERR_FAIL_COND_V(tangents.is_empty(), ERR_FILE_CORRUPT);
for (int vert = 0; vert < norms.size(); vert++) {
Vector3 tan = Vector3(tangents[vert * 4 + 0], tangents[vert * 4 + 1], tangents[vert * 4 + 2]);
if (std::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);
uses_uvs = false;
}
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).strip_edges();
}
if (l.begins_with("usemtl ")) {
current_material = l.replace("usemtl", "").strip_edges();
}
if (l.begins_with("g ")) {
current_group = l.substr(2).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_generate_lightmap_uv2) {
Vector<uint8_t> lightmap_cache;
mesh->lightmap_unwrap_cached(Transform3D(), p_generate_lightmap_uv2_texel_size, p_src_lightmap_cache, lightmap_cache);
if (!lightmap_cache.is_empty()) {
if (r_lightmap_caches.is_empty()) {
r_lightmap_caches.push_back(lightmap_cache);
} else {
// MD5 is stored at the beginning of the cache data.
const String new_md5 = String::md5(lightmap_cache.ptr());
for (int i = 0; i < r_lightmap_caches.size(); i++) {
const String md5 = String::md5(r_lightmap_caches[i].ptr());
if (new_md5 < md5) {
r_lightmap_caches.insert(i, lightmap_cache);
break;
}
if (new_md5 == md5) {
break;
}
}
}
}
}
if (p_generate_lods) {
// Use normal merge/split angles that match the defaults used for 3D scene importing.
mesh->generate_lods(60.0f, {});
}
if (p_generate_shadow_mesh) {
mesh->create_shadow_mesh();
}
mesh->optimize_indices();
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;
// LOD, shadow mesh and lightmap UV2 generation are handled by ResourceImporterScene in this case,
// so disable it within the OBJ mesh import.
Vector<Vector<uint8_t>> mesh_lightmap_caches;
Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, false, false, false, 0.2, PackedByteArray(), Vector3(1, 1, 1), Vector3(0, 0, 0), p_flags & IMPORT_FORCE_DISABLE_MESH_COMPRESSION, mesh_lightmap_caches, 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");
}
////////////////////////////////////////////////////
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::BOOL, "generate_lods"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_shadow_mesh"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_lightmap_uv2", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "generate_lightmap_uv2_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.2));
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, "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 {
if (p_option == "generate_lightmap_uv2_texel_size" && !p_options["generate_lightmap_uv2"]) {
// Only display the lightmap texel size import option when lightmap UV2 generation is enabled.
return false;
}
return true;
}
Error ResourceImporterOBJ::import(ResourceUID::ID p_source_id, 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;
Vector<uint8_t> src_lightmap_cache;
Vector<Vector<uint8_t>> mesh_lightmap_caches;
Error err;
{
src_lightmap_cache = FileAccess::get_file_as_bytes(p_source_file + ".unwrap_cache", &err);
if (err != OK) {
src_lightmap_cache.clear();
}
}
err = _parse_obj(p_source_file, meshes, true, p_options["generate_tangents"], p_options["generate_lods"], p_options["generate_shadow_mesh"], p_options["generate_lightmap_uv2"], p_options["generate_lightmap_uv2_texel_size"], src_lightmap_cache, p_options["scale_mesh"], p_options["offset_mesh"], p_options["force_disable_mesh_compression"], mesh_lightmap_caches, nullptr);
if (mesh_lightmap_caches.size()) {
Ref<FileAccess> f = FileAccess::open(p_source_file + ".unwrap_cache", FileAccess::WRITE);
if (f.is_valid()) {
f->store_32(mesh_lightmap_caches.size());
for (int i = 0; i < mesh_lightmap_caches.size(); i++) {
String md5 = String::md5(mesh_lightmap_caches[i].ptr());
f->store_buffer(mesh_lightmap_caches[i].ptr(), mesh_lightmap_caches[i].size());
}
}
}
err = OK;
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;
}

View File

@@ -0,0 +1,61 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "resource_importer_scene.h"
class EditorOBJImporter : public EditorSceneFormatImporter {
GDCLASS(EditorOBJImporter, EditorSceneFormatImporter);
public:
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;
};
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(ResourceUID::ID p_source_id, 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;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,520 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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/sphere_shape_3d.h"
#include "scene/resources/animation.h"
#include "scene/resources/mesh.h"
class AnimationPlayer;
class ImporterMesh;
class Material;
class EditorSceneFormatImporter : public RefCounted {
GDCLASS(EditorSceneFormatImporter, RefCounted);
List<ResourceImporter::ImportOption> *current_option_list = nullptr;
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(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,
};
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_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, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options);
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {}
};
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);
};
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, const String &p_scene_import_type, 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, const String &p_scene_import_type, 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);
};
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(ResourceUID::ID p_source_id, const String &p_hash_suffix, 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);
Node *_replace_node_with_type_and_script(Node *p_node, String p_node_type, Ref<Script> p_script);
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);
String _scene_import_type = "PackedScene";
public:
static const String material_extension[3];
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();
String get_scene_import_type() const { return _scene_import_type; }
void set_scene_import_type(const String &p_type) { _scene_import_type = p_type; }
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, const HashMap<StringName, Variant> &p_options);
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, const String &p_source_file, const HashMap<StringName, Variant> &p_options);
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(ResourceUID::ID p_source_id, 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;
ResourceImporterScene(const String &p_scene_import_type = "PackedScene", 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 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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,254 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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/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/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;
Button *animation_toggle_skeleton_visibility = 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;
MeshInstance3D *bones_mesh_preview = nullptr;
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;
bool _get_current(const StringName &p_name, Variant &r_ret) const;
void _set_default(const StringName &p_name, const Variant &p_value);
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 _animation_update_skeleton_visibility();
void _material_tree_selected();
void _mesh_tree_selected();
void _scene_tree_selected();
void _skeleton_tree_entered(Skeleton3D *p_skeleton);
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, const String &p_scene_import_type = "PackedScene");
static SceneImportSettingsDialog *get_singleton();
Node *get_selected_node();
SceneImportSettingsDialog();
~SceneImportSettingsDialog();
};

7
editor/import/SCsub Normal file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.editor_sources, "*.cpp")
env.add_source_files(env.editor_sources, "3d/*.cpp")

View File

@@ -0,0 +1,414 @@
/* XPM */
static const char * atlas_import_failed_xpm[] = {
"128 128 283 2",
" c None",
". c #FFFFFF",
"+ c #FFDADA",
"@ c #FF0000",
"# c #FFD8D8",
"$ c #FFF7F7",
"% c #FF2E2E",
"& c #FFD4D4",
"* c #FFD6D6",
"= c #FFE3E3",
"- c #FFB3B3",
"; c #FFC8C8",
"> c #FF3535",
", c #FF8D8D",
"' c #FF7878",
") c #FF6E6E",
"! c #FFB5B5",
"~ c #FF0D0D",
"{ c #FFF0F0",
"] c #FFE8E8",
"^ c #FFC2C2",
"/ c #FFEDED",
"( c #FFBBBB",
"_ c #FFB9B9",
": c #FFA4A4",
"< c #FFFEFE",
"[ c #FFD9D9",
"} c #FF9393",
"| c #FF5858",
"1 c #FF3232",
"2 c #FF7575",
"3 c #FFC9C9",
"4 c #FFFCFC",
"5 c #FFBDBD",
"6 c #FF3838",
"7 c #FF9494",
"8 c #FFE2E2",
"9 c #FFD1D1",
"0 c #FFDEDE",
"a c #FFCACA",
"b c #FF6969",
"c c #FF8484",
"d c #FFEAEA",
"e c #FFE9E9",
"f c #FF3B3B",
"g c #FFC0C0",
"h c #FF6868",
"i c #FF7373",
"j c #FFF6F6",
"k c #FFADAD",
"l c #FF5D5D",
"m c #FF2626",
"n c #FF5C5C",
"o c #FFABAB",
"p c #FFCECE",
"q c #FF7070",
"r c #FF5555",
"s c #FF1C1C",
"t c #FFF4F4",
"u c #FF8282",
"v c #FF6060",
"w c #FFE7E7",
"x c #FF9D9D",
"y c #FF5656",
"z c #FF4242",
"A c #FF9B9B",
"B c #FFD0D0",
"C c #FFF8F8",
"D c #FF6A6A",
"E c #FF5151",
"F c #FFFBFB",
"G c #FF4949",
"H c #FFCDCD",
"I c #FFDDDD",
"J c #FF9E9E",
"K c #FFF9F9",
"L c #FFDCDC",
"M c #FF8F8F",
"N c #FFCBCB",
"O c #FFF5F5",
"P c #FF4747",
"Q c #FF9C9C",
"R c #FFEEEE",
"S c #FFFAFA",
"T c #FF1616",
"U c #FF8888",
"V c #FFC5C5",
"W c #FF2222",
"X c #FF4B4B",
"Y c #FFB8B8",
"Z c #FF7F7F",
"` c #FFE6E6",
" . c #FF8080",
".. c #FFB4B4",
"+. c #FFC3C3",
"@. c #FFD2D2",
"#. c #FFD7D7",
"$. c #FFDFDF",
"%. c #FFB7B7",
"&. c #FFF1F1",
"*. c #FF6262",
"=. c #FF8A8A",
"-. c #FFA9A9",
";. c #FFAEAE",
">. c #FFAAAA",
",. c #FF8B8B",
"'. c #FF4F4F",
"). c #FFFDFD",
"!. c #FFA3A3",
"~. c #FF2A2A",
"{. c #FFCFCF",
"]. c #FF8585",
"^. c #FF7676",
"/. c #FFD3D3",
"(. c #FFD5D5",
"_. c #FF8181",
":. c #FFC6C6",
"<. c #FFDBDB",
"[. c #FF9090",
"}. c #FFAFAF",
"|. c #FFA1A1",
"1. c #FFBABA",
"2. c #FF6C6C",
"3. c #FF5F5F",
"4. c #FF3D3D",
"5. c #FF9999",
"6. c #FFE0E0",
"7. c #FF8383",
"8. c #FFEFEF",
"9. c #FFF3F3",
"0. c #FFA8A8",
"a. c #FFB6B6",
"b. c #FF9F9F",
"c. c #FF4545",
"d. c #FFE5E5",
"e. c #FFE4E4",
"f. c #FFC7C7",
"g. c #FF6565",
"h. c #FFACAC",
"i. c #FF5A5A",
"j. c #FF7272",
"k. c #FF7C7C",
"l. c #FFBFBF",
"m. c #FF7171",
"n. c #FFECEC",
"o. c #FF8989",
"p. c #FF7777",
"q. c #FFC4C4",
"r. c #FF9898",
"s. c #FF8C8C",
"t. c #FF7A7A",
"u. c #FF8E8E",
"v. c #FFF2F2",
"w. c #FF9797",
"x. c #FFC1C1",
"y. c #FFA6A6",
"z. c #FFEBEB",
"A. c #FF4040",
"B. c #EDEDED",
"C. c #000000",
"D. c #AAAAAA",
"E. c #F6F6F6",
"F. c #1C1C1C",
"G. c #888888",
"H. c #7C7C7C",
"I. c #626262",
"J. c #B3B3B3",
"K. c #2A2A2A",
"L. c #959595",
"M. c #FDFDFD",
"N. c #C5C5C5",
"O. c #666666",
"P. c #353535",
"Q. c #777777",
"R. c #DEDEDE",
"S. c #6C6C6C",
"T. c #F5F5F5",
"U. c #ADADAD",
"V. c #DDDDDD",
"W. c #D8D8D8",
"X. c #B4B4B4",
"Y. c #FAFAFA",
"Z. c #949494",
"`. c #3B3B3B",
" + c #A8A8A8",
".+ c #C8C8C8",
"++ c #D4D4D4",
"@+ c #B9B9B9",
"#+ c #2E2E2E",
"$+ c #FEFEFE",
"%+ c #BABABA",
"&+ c #FCFCFC",
"*+ c #B2B2B2",
"=+ c #CACACA",
"-+ c #696969",
";+ c #222222",
">+ c #F2F2F2",
",+ c #555555",
"'+ c #C4C4C4",
")+ c #EBEBEB",
"!+ c #727272",
"~+ c #585858",
"{+ c #0D0D0D",
"]+ c #B1B1B1",
"^+ c #E5E5E5",
"/+ c #C0C0C0",
"(+ c #8F8F8F",
"_+ c #4D4D4D",
":+ c #F4F4F4",
"<+ c #7D7D7D",
"[+ c #E4E4E4",
"}+ c #F3F3F3",
"|+ c #383838",
"1+ c #A9A9A9",
"2+ c #D6D6D6",
"3+ c #D5D5D5",
"4+ c #5F5F5F",
"5+ c #C6C6C6",
"6+ c #E2E2E2",
"7+ c #FBFBFB",
"8+ c #404040",
"9+ c #909090",
"0+ c #EEEEEE",
"a+ c #878787",
"b+ c #E8E8E8",
"c+ c #494949",
"d+ c #424242",
"e+ c #E6E6E6",
"f+ c #CFCFCF",
"g+ c #DCDCDC",
"h+ c #161616",
"i+ c #BBBBBB",
"j+ c #CCCCCC",
"k+ c #B0B0B0",
"l+ c #C7C7C7",
"m+ c #858585",
"n+ c #F8F8F8",
"o+ c #D7D7D7",
"p+ c #BDBDBD",
"q+ c #ECECEC",
"r+ c #939393",
"s+ c #A1A1A1",
"t+ c #7A7A7A",
"u+ c #4B4B4B",
"v+ c #E9E9E9",
"w+ c #717171",
"x+ c #AFAFAF",
"y+ c #454545",
"z+ c #F9F9F9",
"A+ c #DBDBDB",
"B+ c #C1C1C1",
"C+ c #707070",
"D+ c #323232",
"E+ c #9D9D9D",
"F+ c #D1D1D1",
"G+ c #6D6D6D",
"H+ c #262626",
"I+ c #6E6E6E",
"J+ c #808080",
"K+ c #BFBFBF",
"L+ c #999999",
"M+ c #F1F1F1",
"N+ c #DADADA",
"O+ c #9F9F9F",
"P+ c #8B8B8B",
"Q+ c #7F7F7F",
"R+ c #9E9E9E",
"S+ c #F0F0F0",
"T+ c #A4A4A4",
"U+ c #A5A5A5",
"V+ c #CDCDCD",
"W+ c #CBCBCB",
"X+ c #9B9B9B",
"Y+ c #D9D9D9",
"Z+ c #A0A0A0",
"`+ c #9C9C9C",
" @ c #C2C2C2",
".@ c #636363",
"+@ c #D0D0D0",
"@@ c #6A6A6A",
"#@ c #898989",
"$@ c #C3C3C3",
"%@ c #A7A7A7",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . + @ # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . $ % @ & . . . . . * @ + . . . . + @ # . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * @ + . . . . . . . . . . . . . ",
". . . . . . . . . . . . . ; @ > , . . . . . * @ + . . . . + @ # . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * @ + . . . . . . . . . . . . . ",
". . . . . . . . . . . . . ' ) ! ~ { . . . . * @ + . . . . + @ # . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * @ + . . . . . . . . . . . . . ",
". . . . . . . . . . . . ] @ ^ / @ ( . . . _ @ @ @ @ @ : . + @ # . . < [ } | 1 2 3 . . . . 4 5 ) 6 | 7 8 . . . . . . . . = @ - . . 9 @ 0 a b > c d . e , f | g . . . . 9 @ 0 a h % i & . . . . j k l m n o j . . . 9 @ 0 p q m r @ @ @ @ @ : . . . . . . . . . . ",
". . . . . . . . . . . . k s t . u v < . . _ @ @ @ @ @ : . + @ # . . w @ @ @ @ @ @ 5 . . . k @ @ @ @ @ % . . . . . . . . = @ - . . 9 @ x @ @ @ @ y d z @ @ @ @ * . . . 9 @ A @ @ @ @ @ B . . C D @ @ @ @ @ h C . . 9 @ x @ @ @ E @ @ @ @ @ : . . . . . . . . . . ",
". . . . . . . . . . . F G 7 . . H @ I . . . * @ + . . . . + @ # . . ] J 8 j K 0 h 6 K . . l l = F j L M . . . . . . . . = @ - . . 9 @ % N j O J @ P Q R S & T U . . . 9 @ s ^ O j V W X F . Y @ Z ` S w .@ ... . 9 @ s +.t . . * @ + . . . . . . . . . . . . . ",
". . . . . . . . . . . @.@ #.. . K 6 x . . . * @ + . . . . + @ # . . . . . . . . # @ $.. . ' M . . . . . . . . . . . . . = @ - . . 9 @ %.. . . &.@ *.4 . . . =.r . . . 9 @ -.. . . . ;.@ 0 . q 6 t . . . t f h . . 9 @ >.. . . . * @ + . . . . . . . . . . . . . ",
". . . . . . . . . . . ,.'.).. . . !.~.j . . * @ + . . . . + @ # . . . {.].P ~ @ @ @ 9 . . 8 r ^.- /.j . . . . . . . . . = @ - . . 9 @ + . . . . @ A . . . . !.~ . . . 9 @ (.. . . . # @ N . 1 _.. . . . . c s . . 9 @ (.. . . . * @ + . . . . . . . . . . . . . ",
". . . . . . . . . . { ~ @ @ @ @ @ @ @ :.. . * @ + . . . . + @ # . . ^ @ @ @ @ @ @ @ H . . . F <._ [.> }.. . . . . . . . = @ - . . 9 @ 0 . . . . @ |.. . . . : @ . . . 9 @ (.. . . . # @ N . 1 u . . . . . c s . . 9 @ 0 . . . . * @ + . . . . . . . . . . . . . ",
". . . . . . . . . . 1.@ @ @ @ @ @ @ @ ^.. . #.@ # . . . . + @ # . . 2.r $.$ < . [ @ H . . . . . . . + @ * . . . . . . . = @ - . . 9 @ 0 . . . . @ |.. . . . : @ . . . 9 @ -.. . . . ;.@ 0 . q 6 t . . . O f h . . 9 @ 0 . . . . #.@ # . . . . . . . . . . . . . ",
". . . . . . . . . < 3.4.K . . . . . 5.@ w . 6.@ ;.F . . . + @ # . . 2.n ` 4 $ + *.@ H . 4 7.@.8.4 9.k @ ^ . . . . . . . = @ - . . 9 @ 0 . . . . @ |.. . . . : @ . . . 9 @ s ^ O j V W X F . %.@ u w F ] u @ - . . 9 @ 0 . . . . 6.@ ;.F . . . . . . . . . . . . ",
". . . . . . . . . L @ 0.. . . . . . L @ o . K > @ @ @ : . + @ # . . a.@ @ @ @ @ b.@ H . F @ @ @ @ @ @ > / . . . . . . . = @ - . . 9 @ 0 . . . . @ |.. . . . : @ . . . 9 @ A @ @ @ @ @ B . . C D @ @ @ @ @ h $ . . 9 @ 0 . . . . K > @ @ @ : . . . . . . . . . . ",
". . . . . . . . . A @ ` . . . . . . < *.c.F . # ^.~.@ : . + @ # . . 4 _ *.% q N d.@ H . . e.: h % l b.e . . . . . . . . = @ - . . 9 @ 0 . . . . @ |.. . . . : @ . . . 9 @ 0 f.g.~.i /.. . . . j h.n W i.>.O . . . 9 @ 0 . . . . . # ^.~.@ : . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 @ 0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 @ 0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 @ 0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 @ 0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + @ # . . + @ # . . . . . . . . . . . . . . . . . . . . j.k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ @ @ @ @ @ @ # . . . . . . . . . . + @ # . . + @ # . . . . . . . . . . . . . . . . . . . . j.k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ @ @ @ @ @ @ # . . . . . . . . . . . . . . . + @ # . . . . . . . . . . . . . . . . . . . . j.k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . . . . . . . . . . . . . . . . . + @ # . . . . . . . . . . . . . . . . . . . . j.k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . < [ } | 1 2 3 . . . . + @ # . . + @ # . . . 4 l.m.% G 5.n.. . . . &.Q z c.x O j.k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . w @ @ @ @ @ @ 5 . . . + @ # . . + @ # . . ).o.@ @ @ @ @ c./ . . $ v @ @ @ @ p.2.k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ @ @ @ @ @ U ] J 8 j K 0 h 6 K . . + @ # . . + @ # . . q.@ } w F O q.s r.. . a.@ s.d S 8 ) @ k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ @ @ @ @ @ U . . . . . . # @ $.. . + @ # . . + @ # . . t.| ).. . . . !.> < . q G C . . . d @ k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . . {.].P ~ @ @ @ 9 . . + @ # . . + @ # . . > @ @ @ @ @ @ @ @ O . % u.. . . . . y k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . ^ @ @ @ @ @ @ @ H . . + @ # . . + @ # . . 1 @ @ @ @ @ @ @ @ v.. % u.. . . . . r k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . 2.r $.$ < . [ @ H . . + @ # . . + @ # . . ^.h < . . . . . . . . q G C . . . d @ k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . 2.n ` 4 $ + *.@ H . . + @ # . . + @ # . . +.@ w.e.C S d x.y.. . a.@ s.z.S 8 ) @ k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . a.@ @ @ @ @ b.@ H . . + @ # . . + @ # . . ).M @ @ @ @ @ @ s.. . $ v @ @ @ @ ^.2.k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . = @ - . . . . . 4 _ *.% q N d.@ H . . + @ # . . + @ # . . . ).f.k.6 z ' Y v.. . . &.A A.z A O j.k.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.D.B.C.D.B.C.D.. . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . E.C.C.C.C.F.. B.C.D.B.C.D.B.C.D.. . . . C.G.. . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . E.C.H.. . . . . . . B.C.D.B.C.D.. . . . C.G.. . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . E.C.H.. . . . B.C.D.B.C.D.B.C.D.. . . I.C.C.C.J.B.C.C.C.K.L.M.. N.O.P.Q.R.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . E.C.C.C.C.H.. B.C.D.B.C.D.B.C.D.. . . . C.G.. . B.C.S.T.U.C.V.W.C.X.Y.Z.`.E.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . E.C.H.. . . . B.C.D.B.C.D.B.C.D.. . . . C.G.. . B.C. +. .+C.++@+C.C.C.C.C.R.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . E.C.H.. . . . B.C.D.B.C.D.B.C.D.. . . . #+Q.$+. B.C.D.. .+C.++W.C.%+&+. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . E.C.H.. . . . B.C.D.B.C.D.B.C.D.. . . . *+`.C.=+B.C.D.. .+C.++. N.-+;+C.C.Y.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . B.C.D.B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Y.C.>+,+R.. . '+C.C.)+. . . C.G.. . B.C.D.. . . . . . . . . . . . . . . . E.C.C.C.C.F.. B.C.D.B.C.D.. . . . . . . Y.C.>+,+R.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Y.C.>+,+R.. . !+~+{+]+. . . C.G.. . B.C.D.. . . . . . . . . . . . . . . . E.C.H.. . . . . . . B.C.D.. . . . . . . Y.C.>+,+R.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Y.C.>+,+R.. ^+C./+(+_+&+. I.C.C.C.J.B.C.D.:+C.C.P.<+[+. }+G.|+C.C.>+. . . E.C.H.. . . . B.C.D.B.C.D.. N.O.P.Q.R.. Y.C.>+,+R.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1+;+T.2+C.3+. . C.G.. . B.C.D.. . . T.4+Q.. 5+C.X.6+Y.. . . . E.C.C.C.C.H.. B.C.D.B.C.D.W.C.X.Y.Z.`.E.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7+8+C.C.C.C.9+. . C.G.. . B.C.D.0+a+8+C.C.|+. b+c+C.C.d+e+. . . E.C.H.. . . . B.C.D.B.C.D.@+C.C.C.C.C.R.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . f+C.g+. . J.h+>+. #+Q.$+. B.C.D.i+C.j+0+,+#+. . &+e+k+C.l+. . . E.C.H.. . . . B.C.D.B.C.D.W.C.%+&+. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . m+h+n+. . o+C.p+. *+`.C.=+B.C.D.q+!+{+C.C.#+. =+C.C.8+r+E.. . . E.C.H.. . . . B.C.D.B.C.D.. N.-+;+C.C.Y.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . V.4+C.s+C.U.. . . . . . . B.C.D.. . . . N.C.W.. . . B.C.D.. . . . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . t+u+Y.v+C.U.. . . . . . . B.C.D.. . . . N.C.W.. . . B.C.D.. . . . . . . . . . . C.G.. . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . u+w+. . . . . . . . . . . B.C.D.. . . . N.C.W.. . . . . . . . . . . . . . . . . C.G.. . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . H.C.C.C.C.C.U.. N.O.P.Q.R.. B.C.D.. x+d+C.C.C.W.. . . B.C.D.B.C.C.C.K.L.M.. . . I.C.C.C.J.B.C.C.C.K.L.M.. N.O.P.Q.R.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . y+!+. v+C.U.W.C.X.Y.Z.`.E.B.C.D.2+C.x+z+(+C.W.. . . B.C.D.B.C.S.T.U.C.V.. . . . C.G.. . B.C.S.T.U.C.V.W.C.X.Y.Z.`.E.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . y+!+. v+C.U.@+C.C.C.C.C.R.B.C.D.%+C.A+. B+C.W.. . . B.C.D.B.C. +. .+C.++. . . . C.G.. . B.C. +. .+C.++@+C.C.C.C.C.R.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . y+!+. v+C.U.W.C.%+&+. . . B.C.D.2+C.k+z+9+C.W.. . . B.C.D.B.C.D.. .+C.++. . . . #+Q.$+. B.C.D.. .+C.++W.C.%+&+. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . y+!+. v+C.U.. N.-+;+C.C.Y.B.C.D.. x+d+C.C.C.W.. . . B.C.D.B.C.D.. .+C.++. . . . *+`.C.=+B.C.D.. .+C.++. N.-+;+C.C.Y.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . N.C.W.. . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . N.C.W.. . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.G.. . . . . . . . . N.C.W.. . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . N.C.W.. . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.G.. . . . . . . . . N.C.W.. . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . N.C.W.. . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . B.C.D.)+C.C.C.K.C+O.K.!+B.B.C.C.C.D+E+7+. '+O.P.C+F+. B.C.C.C.,+I.C.C.C.J.. . . . x+d+C.C.C.W.. '+O.P.C+F+. . .+G+H+C.R.B.C.D.>+I+I.q+. . . :+C.C.P.<+[+. B.C.C.C.K.L.M.. x+d+C.C.C.W.. . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . B.C.D.)+C.!+T.J+C.=+g+C.]+B.C.I+E.=+C.K+W.C.]+Y.L+h+b+B.C.O.M+. . C.G.. . . . . 2+C.x+z+(+C.W.W.C.]+Y.L+h+b+N+C.O+z+. . B.C.P+4+Q+T.. . . . . . . T.4+Q.. B.C.S.T.U.C.V.2+C.x+z+(+C.W.. . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . B.C.D.)+C. +. R+C.M+S+C.T+B.C.U+. M+C.E+@+C.V.. =+C.V+B.C. +. . . C.G.. . . . . %+C.A+. B+C.W.@+C.V.. =+C.V+@+C.A+. . . B.C.C.C.R.. . . . . 0+a+8+C.C.|+. B.C. +. .+C.++%+C.A+. B+C.W.. . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . B.C.D.)+C.D.. R+C.>+S+C.T+B.C.C+E.W+C.K+W.C.*+Y.X+{+b+B.C.D.. . . #+Q.$+. . . . 2+C.k+z+9+C.W.W.C.*+Y.X+{+b+Y+C.s+z+. . B.C.Z+m+|+V.. . . . i+C.j+0+,+#+. B.C.D.. .+C.++2+C.k+z+9+C.W.. . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . B.C.D.)+C.D.. R+C.>+S+C.T+B.C.C.C.D+`+7+. @.@D+G++@. B.C.D.. . . *+`.C.=+. . . . x+d+C.C.C.W.. @.@D+G++@. . l+@@H+C.R.B.C.D.Y.#@K.2+. . . q+!+{+C.C.#+. B.C.D.. .+C.++. x+d+C.C.C.W.. . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.G.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.G.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.C.C.,+. N.O.P.Q.R.. . . . . B.C.D.)+C.C.C.K.C+O.K.!+B.B.C.C.C.D+E+7+. '+O.P.C+F+. B.C.C.C.,+I.C.C.C.J.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.O.M+. W.C.X.Y.Z.`.E.$@C.C.%@B.C.D.)+C.!+T.J+C.=+g+C.]+B.C.I+E.=+C.K+W.C.]+Y.L+h+b+B.C.O.M+. . C.G.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C. +. . @+C.C.C.C.C.R.. . . . B.C.D.)+C. +. R+C.M+S+C.T+B.C.U+. M+C.E+@+C.V.. =+C.V+B.C. +. . . C.G.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.D.. . W.C.%+&+. . . . . . . B.C.D.)+C.D.. R+C.>+S+C.T+B.C.C+E.W+C.K+W.C.*+Y.X+{+b+B.C.D.. . . #+Q.$+. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.D.. . . N.-+;+C.C.Y.. . . . B.C.D.)+C.D.. R+C.>+S+C.T+B.C.C.C.D+`+7+. @.@D+G++@. B.C.D.. . . *+`.C.=+. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.C.D.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ",
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "};

View File

@@ -0,0 +1,672 @@
/**************************************************************************/
/* audio_stream_import_settings.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 "audio_stream_import_settings.h"
#include "editor/audio/audio_stream_preview.h"
#include "editor/editor_string_names.h"
#include "editor/file_system/editor_file_system.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/check_box.h"
AudioStreamImportSettingsDialog *AudioStreamImportSettingsDialog::singleton = nullptr;
void AudioStreamImportSettingsDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", callable_mp(this, &AudioStreamImportSettingsDialog::_preview_changed));
connect(SceneStringName(confirmed), callable_mp(this, &AudioStreamImportSettingsDialog::_reimport));
} break;
case NOTIFICATION_THEME_CHANGED: {
_play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
_stop_button->set_button_icon(get_editor_theme_icon(SNAME("Stop")));
_preview->set_color(get_theme_color(SNAME("dark_color_2"), EditorStringName(Editor)));
color_rect->set_color(get_theme_color(SNAME("dark_color_1"), EditorStringName(Editor)));
_current_label->begin_bulk_theme_override();
_current_label->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("status_source"), EditorStringName(EditorFonts)));
_current_label->add_theme_font_size_override(SceneStringName(font_size), get_theme_font_size(SNAME("status_source_size"), EditorStringName(EditorFonts)));
_current_label->end_bulk_theme_override();
_duration_label->begin_bulk_theme_override();
_duration_label->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("status_source"), EditorStringName(EditorFonts)));
_duration_label->add_theme_font_size_override(SceneStringName(font_size), get_theme_font_size(SNAME("status_source_size"), EditorStringName(EditorFonts)));
_duration_label->end_bulk_theme_override();
zoom_in->set_button_icon(get_editor_theme_icon(SNAME("ZoomMore")));
zoom_out->set_button_icon(get_editor_theme_icon(SNAME("ZoomLess")));
zoom_reset->set_button_icon(get_editor_theme_icon(SNAME("ZoomReset")));
_indicator->queue_redraw();
_preview->queue_redraw();
} break;
case NOTIFICATION_PROCESS: {
_current = _player->get_playback_position();
_indicator->queue_redraw();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
_stop();
}
} break;
}
}
void AudioStreamImportSettingsDialog::_draw_preview() {
Rect2 rect = _preview->get_rect();
Size2 rect_size = rect.size;
int width = rect_size.width;
Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
float preview_offset = zoom_bar->get_value();
float preview_len = zoom_bar->get_page();
Ref<Font> beat_font = get_theme_font(SNAME("main"), EditorStringName(EditorFonts));
int main_size = get_theme_font_size(SNAME("main_size"), EditorStringName(EditorFonts));
Vector<Vector2> points;
points.resize(width * 2);
Color color_active = get_theme_color(SNAME("contrast_color_2"), EditorStringName(Editor));
Color color_inactive = color_active;
color_inactive.a *= 0.5;
Vector<Color> colors;
colors.resize(width);
float inactive_from = 1e20;
float beat_size = 0;
int last_beat = 0;
if (stream->get_bpm() > 0) {
beat_size = 60 / float(stream->get_bpm());
int y_ofs = beat_font->get_height(main_size) + 4 * EDSCALE;
rect.position.y += y_ofs;
rect.size.y -= y_ofs;
if (stream->get_beat_count() > 0) {
last_beat = stream->get_beat_count();
inactive_from = last_beat * beat_size;
}
}
for (int i = 0; i < width; i++) {
float ofs = preview_offset + i * preview_len / rect_size.width;
float ofs_n = preview_offset + (i + 1) * preview_len / rect_size.width;
float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5;
float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5;
int idx = i;
points.write[idx * 2 + 0] = Vector2(i + 1, rect.position.y + min * rect.size.y);
points.write[idx * 2 + 1] = Vector2(i + 1, rect.position.y + max * rect.size.y);
colors.write[idx] = ofs > inactive_from ? color_inactive : color_active;
}
if (!points.is_empty()) {
RS::get_singleton()->canvas_item_add_multiline(_preview->get_canvas_item(), points, colors);
}
if (beat_size) {
Color beat_color = Color(1, 1, 1, 1);
Color final_beat_color = beat_color;
Color bar_color = beat_color;
beat_color.a *= 0.4;
bar_color.a *= 0.6;
int prev_beat = 0; // Do not draw beat zero
Color color_bg = color_active;
color_bg.a *= 0.2;
_preview->draw_rect(Rect2(0, 0, rect.size.width, rect.position.y), color_bg);
int bar_beats = stream->get_bar_beats();
int last_text_end_x = 0;
for (int i = 0; i < width; i++) {
float ofs = preview_offset + i * preview_len / rect_size.width;
int beat = int(ofs / beat_size);
if (beat != prev_beat) {
String text = itos(beat);
int text_w = beat_font->get_string_size(text).width;
if (i - text_w / 2 > last_text_end_x + 2 * EDSCALE) {
int x_ofs = i - text_w / 2;
_preview->draw_string(beat_font, Point2(x_ofs, 2 * EDSCALE + beat_font->get_ascent(main_size)), text, HORIZONTAL_ALIGNMENT_LEFT, rect.size.width - x_ofs, Font::DEFAULT_FONT_SIZE, color_active);
last_text_end_x = i + text_w / 2;
}
if (beat == last_beat) {
_preview->draw_rect(Rect2i(i, rect.position.y, 2, rect.size.height), final_beat_color);
// Darken subsequent beats
beat_color.a *= 0.3;
color_active.a *= 0.3;
} else {
_preview->draw_rect(Rect2i(i, rect.position.y, 1, rect.size.height), (beat % bar_beats) == 0 ? bar_color : beat_color);
}
prev_beat = beat;
}
}
}
}
void AudioStreamImportSettingsDialog::_preview_changed(ObjectID p_which) {
if (stream.is_valid() && stream->get_instance_id() == p_which) {
_preview->queue_redraw();
}
}
void AudioStreamImportSettingsDialog::_preview_zoom_in() {
if (stream.is_null()) {
return;
}
float page_size = zoom_bar->get_page();
zoom_bar->set_page(page_size * 0.5);
zoom_bar->set_value(zoom_bar->get_value() + page_size * 0.25);
zoom_bar->show();
_preview->queue_redraw();
_indicator->queue_redraw();
}
void AudioStreamImportSettingsDialog::_preview_zoom_out() {
if (stream.is_null()) {
return;
}
float page_size = zoom_bar->get_page();
zoom_bar->set_page(MIN(zoom_bar->get_max(), page_size * 2.0));
zoom_bar->set_value(zoom_bar->get_value() - page_size * 0.5);
if (zoom_bar->get_value() == 0) {
zoom_bar->hide();
}
_preview->queue_redraw();
_indicator->queue_redraw();
}
void AudioStreamImportSettingsDialog::_preview_zoom_reset() {
if (stream.is_null()) {
return;
}
zoom_bar->set_max(stream->get_length());
zoom_bar->set_page(zoom_bar->get_max());
zoom_bar->set_value(0);
zoom_bar->hide();
_preview->queue_redraw();
_indicator->queue_redraw();
}
void AudioStreamImportSettingsDialog::_preview_zoom_offset_changed(double) {
_preview->queue_redraw();
_indicator->queue_redraw();
}
void AudioStreamImportSettingsDialog::_audio_changed() {
if (!is_visible()) {
return;
}
_preview->queue_redraw();
_indicator->queue_redraw();
color_rect->queue_redraw();
}
void AudioStreamImportSettingsDialog::_play() {
if (_player->is_playing()) {
// '_pausing' variable indicates that we want to pause the audio player, not stop it. See '_on_finished()'.
_pausing = true;
_player->stop();
_play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
set_process(false);
} else {
_player->play(_current);
_play_button->set_button_icon(get_editor_theme_icon(SNAME("Pause")));
set_process(true);
}
}
void AudioStreamImportSettingsDialog::_stop() {
_player->stop();
_play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
_current = 0;
_indicator->queue_redraw();
set_process(false);
}
void AudioStreamImportSettingsDialog::_on_finished() {
_play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
if (!_pausing) {
_current = 0;
_indicator->queue_redraw();
} else {
_pausing = false;
}
set_process(false);
}
void AudioStreamImportSettingsDialog::_draw_indicator() {
if (stream.is_null()) {
return;
}
Rect2 rect = _preview->get_rect();
Ref<Font> beat_font = get_theme_font(SNAME("main"), EditorStringName(EditorFonts));
int main_size = get_theme_font_size(SNAME("main_size"), EditorStringName(EditorFonts));
if (stream->get_bpm() > 0) {
int y_ofs = beat_font->get_height(main_size) + 4 * EDSCALE;
rect.position.y += y_ofs;
rect.size.height -= y_ofs;
}
_current_label->set_text(String::num(_current, 2).pad_decimals(2) + " /");
float ofs_x = (_current - zoom_bar->get_value()) * rect.size.width / zoom_bar->get_page();
if (ofs_x < 0 || ofs_x >= rect.size.width) {
return;
}
const Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
_indicator->draw_line(Point2(ofs_x, rect.position.y), Point2(ofs_x, rect.position.y + rect.size.height), color, Math::round(2 * EDSCALE));
_indicator->draw_texture(
get_editor_theme_icon(SNAME("TimelineIndicator")),
Point2(ofs_x - get_editor_theme_icon(SNAME("TimelineIndicator"))->get_width() * 0.5, rect.position.y),
color);
if (stream->get_bpm() > 0 && _hovering_beat != -1) {
// Draw hovered beat.
float preview_offset = zoom_bar->get_value();
float preview_len = zoom_bar->get_page();
float beat_size = 60 / float(stream->get_bpm());
int prev_beat = 0;
for (int i = 0; i < rect.size.width; i++) {
float ofs = preview_offset + i * preview_len / rect.size.width;
int beat = int(ofs / beat_size);
if (beat != prev_beat) {
String text = itos(beat);
int text_w = beat_font->get_string_size(text).width;
if (i - text_w / 2 > 2 * EDSCALE && beat == _hovering_beat) {
int x_ofs = i - text_w / 2;
_indicator->draw_string(beat_font, Point2(x_ofs, 2 * EDSCALE + beat_font->get_ascent(main_size)), text, HORIZONTAL_ALIGNMENT_LEFT, rect.size.width - x_ofs, Font::DEFAULT_FONT_SIZE, color);
break;
}
prev_beat = beat;
}
}
}
}
void AudioStreamImportSettingsDialog::_on_indicator_mouse_exited() {
_hovering_beat = -1;
_indicator->queue_redraw();
}
void AudioStreamImportSettingsDialog::_on_input_indicator(Ref<InputEvent> p_event) {
const Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
if (stream->get_bpm() > 0) {
int main_size = get_theme_font_size(SNAME("main_size"), EditorStringName(EditorFonts));
Ref<Font> beat_font = get_theme_font(SNAME("main"), EditorStringName(EditorFonts));
int y_ofs = beat_font->get_height(main_size) + 4 * EDSCALE;
if ((!_dragging && mb->get_position().y < y_ofs) || _beat_len_dragging) {
if (mb->is_pressed()) {
_set_beat_len_to(mb->get_position().x);
_beat_len_dragging = true;
} else {
_beat_len_dragging = false;
}
return;
}
}
if (mb->is_pressed()) {
_seek_to(mb->get_position().x);
}
_dragging = mb->is_pressed();
}
const Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (_dragging) {
_seek_to(mm->get_position().x);
}
if (_beat_len_dragging) {
_set_beat_len_to(mm->get_position().x);
}
if (stream->get_bpm() > 0) {
int main_size = get_theme_font_size(SNAME("main_size"), EditorStringName(EditorFonts));
Ref<Font> beat_font = get_theme_font(SNAME("main"), EditorStringName(EditorFonts));
int y_ofs = beat_font->get_height(main_size) + 4 * EDSCALE;
if (mm->get_position().y < y_ofs) {
int new_hovering_beat = _get_beat_at_pos(mm->get_position().x);
if (new_hovering_beat != _hovering_beat) {
_hovering_beat = new_hovering_beat;
_indicator->queue_redraw();
}
} else if (_hovering_beat != -1) {
_hovering_beat = -1;
_indicator->queue_redraw();
}
}
}
}
int AudioStreamImportSettingsDialog::_get_beat_at_pos(real_t p_x) {
float ofs_sec = zoom_bar->get_value() + p_x * zoom_bar->get_page() / _preview->get_size().width;
ofs_sec = CLAMP(ofs_sec, 0, stream->get_length());
float beat_size = 60 / float(stream->get_bpm());
int beat = int(ofs_sec / beat_size + 0.5);
if (beat * beat_size > stream->get_length() + 0.001) { // Stream may end few audio frames before but may still want to use full loop.
beat--;
}
return beat;
}
void AudioStreamImportSettingsDialog::_set_beat_len_to(real_t p_x) {
int beat = _get_beat_at_pos(p_x);
if (beat < 1) {
beat = 1; // Because 0 is disable.
}
updating_settings = true;
beats_enabled->set_pressed(true);
beats_edit->set_value(beat);
updating_settings = false;
_settings_changed();
}
void AudioStreamImportSettingsDialog::_seek_to(real_t p_x) {
_current = zoom_bar->get_value() + p_x / _preview->get_rect().size.x * zoom_bar->get_page();
_current = CLAMP(_current, 0, stream->get_length());
_player->seek(_current);
_indicator->queue_redraw();
}
void AudioStreamImportSettingsDialog::edit(const String &p_path, const String &p_importer, const Ref<AudioStream> &p_stream) {
if (stream.is_valid()) {
stream->disconnect_changed(callable_mp(this, &AudioStreamImportSettingsDialog::_audio_changed));
}
importer = p_importer;
path = p_path;
stream = p_stream;
_player->set_stream(stream);
_current = 0;
String text = String::num(stream->get_length(), 2).pad_decimals(2) + "s";
_duration_label->set_text(text);
if (stream.is_valid()) {
stream->connect_changed(callable_mp(this, &AudioStreamImportSettingsDialog::_audio_changed));
_preview->queue_redraw();
_indicator->queue_redraw();
color_rect->queue_redraw();
} else {
hide();
}
params.clear();
if (stream.is_valid()) {
Ref<ConfigFile> config_file;
config_file.instantiate();
Error err = config_file->load(p_path + ".import");
updating_settings = true;
if (err == OK) {
double bpm = config_file->get_value("params", "bpm", 0);
int beats = config_file->get_value("params", "beat_count", 0);
bpm_edit->set_value(bpm > 0 ? bpm : 120);
bpm_enabled->set_pressed(bpm > 0);
beats_edit->set_value(beats);
beats_enabled->set_pressed(beats > 0);
loop->set_pressed(config_file->get_value("params", "loop", false));
loop_offset->set_value(config_file->get_value("params", "loop_offset", 0));
bar_beats_edit->set_value(config_file->get_value("params", "bar_beats", 4));
Vector<String> keys = config_file->get_section_keys("params");
for (const String &K : keys) {
params[K] = config_file->get_value("params", K);
}
} else {
bpm_edit->set_value(false);
bpm_enabled->set_pressed(false);
beats_edit->set_value(0);
beats_enabled->set_pressed(false);
bar_beats_edit->set_value(4);
loop->set_pressed(false);
loop_offset->set_value(0);
}
_preview_zoom_reset();
updating_settings = false;
_settings_changed();
set_title(vformat(TTR("Audio Stream Importer: %s"), p_path.get_file()));
popup_centered();
}
}
void AudioStreamImportSettingsDialog::_settings_changed() {
if (updating_settings) {
return;
}
updating_settings = true;
stream->call("set_loop", loop->is_pressed());
stream->call("set_loop_offset", loop_offset->get_value());
if (loop->is_pressed()) {
loop_offset->set_editable(true);
} else {
loop_offset->set_editable(false);
}
if (bpm_enabled->is_pressed()) {
stream->call("set_bpm", bpm_edit->get_value());
beats_enabled->set_disabled(false);
beats_edit->set_editable(true);
bar_beats_edit->set_editable(true);
double bpm = bpm_edit->get_value();
if (bpm > 0) {
float beat_size = 60 / float(bpm);
int beat_max = int((stream->get_length() + 0.001) / beat_size);
int current_beat = beats_edit->get_value();
beats_edit->set_max(beat_max);
if (current_beat > beat_max) {
beats_edit->set_value(beat_max);
stream->call("set_beat_count", beat_max);
}
}
stream->call("set_bar_beats", bar_beats_edit->get_value());
} else {
stream->call("set_bpm", 0);
stream->call("set_bar_beats", 4);
beats_enabled->set_disabled(true);
beats_edit->set_editable(false);
bar_beats_edit->set_editable(false);
}
if (bpm_enabled->is_pressed() && beats_enabled->is_pressed()) {
stream->call("set_beat_count", beats_edit->get_value());
} else {
stream->call("set_beat_count", 0);
}
updating_settings = false;
_preview->queue_redraw();
_indicator->queue_redraw();
color_rect->queue_redraw();
}
void AudioStreamImportSettingsDialog::_reimport() {
params["loop"] = loop->is_pressed();
params["loop_offset"] = loop_offset->get_value();
params["bpm"] = bpm_enabled->is_pressed() ? double(bpm_edit->get_value()) : double(0);
params["beat_count"] = (bpm_enabled->is_pressed() && beats_enabled->is_pressed()) ? int(beats_edit->get_value()) : int(0);
params["bar_beats"] = (bpm_enabled->is_pressed()) ? int(bar_beats_edit->get_value()) : int(4);
EditorFileSystem::get_singleton()->reimport_file_with_custom_parameters(path, importer, params);
}
AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() {
get_ok_button()->set_text(TTR("Reimport"));
get_cancel_button()->set_text(TTR("Close"));
VBoxContainer *main_vbox = memnew(VBoxContainer);
add_child(main_vbox);
HBoxContainer *loop_hb = memnew(HBoxContainer);
loop_hb->add_theme_constant_override("separation", 4 * EDSCALE);
loop = memnew(CheckBox);
loop->set_text(TTR("Enable"));
loop->set_tooltip_text(TTR("Enable looping."));
loop->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
loop_hb->add_child(loop);
loop_hb->add_spacer();
loop_hb->add_child(memnew(Label(TTR("Offset:"))));
loop_offset = memnew(SpinBox);
loop_offset->set_accessibility_name(TTRC("Offset:"));
loop_offset->set_max(10000);
loop_offset->set_step(0.001);
loop_offset->set_suffix("s");
loop_offset->set_h_size_flags(Control::SIZE_EXPAND_FILL);
loop_offset->set_stretch_ratio(0.33);
loop_offset->set_tooltip_text(TTR("Loop offset (from beginning). Note that if BPM is set, this setting will be ignored."));
loop_offset->connect(SceneStringName(value_changed), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
loop_hb->add_child(loop_offset);
main_vbox->add_margin_child(TTR("Loop:"), loop_hb);
HBoxContainer *interactive_hb = memnew(HBoxContainer);
interactive_hb->add_theme_constant_override("separation", 4 * EDSCALE);
bpm_enabled = memnew(CheckBox);
bpm_enabled->set_text((TTR("BPM:")));
bpm_enabled->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(bpm_enabled);
bpm_edit = memnew(SpinBox);
bpm_edit->set_max(400);
bpm_edit->set_step(0.01);
bpm_edit->set_accessibility_name(TTRC("BPM:"));
bpm_edit->set_tooltip_text(TTR("Configure the Beats Per Measure (tempo) used for the interactive streams.\nThis is required in order to configure beat information."));
bpm_edit->connect(SceneStringName(value_changed), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(bpm_edit);
interactive_hb->add_spacer();
beats_enabled = memnew(CheckBox);
beats_enabled->set_text(TTR("Beat Count:"));
beats_enabled->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(beats_enabled);
beats_edit = memnew(SpinBox);
beats_edit->set_tooltip_text(TTR("Configure the amount of Beats used for music-aware looping. If zero, it will be autodetected from the length.\nIt is recommended to set this value (either manually or by clicking on a beat number in the preview) to ensure looping works properly."));
beats_edit->set_max(99999);
beats_edit->set_accessibility_name(TTRC("Beat Count:"));
beats_edit->connect(SceneStringName(value_changed), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(beats_edit);
bar_beats_label = memnew(Label(TTR("Bar Beats:")));
interactive_hb->add_child(bar_beats_label);
bar_beats_edit = memnew(SpinBox);
bar_beats_edit->set_tooltip_text(TTR("Configure the Beats Per Bar. This used for music-aware transitions between AudioStreams."));
bar_beats_edit->set_min(2);
bar_beats_edit->set_max(32);
bar_beats_edit->set_accessibility_name(TTRC("Bar Beats:"));
bar_beats_edit->connect(SceneStringName(value_changed), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(bar_beats_edit);
main_vbox->add_margin_child(TTR("Music Playback:"), interactive_hb);
color_rect = memnew(ColorRect);
main_vbox->add_margin_child(TTR("Preview:"), color_rect, true);
color_rect->set_custom_minimum_size(Size2(600, 200) * EDSCALE);
color_rect->set_v_size_flags(Control::SIZE_EXPAND_FILL);
_player = memnew(AudioStreamPlayer);
_player->connect(SceneStringName(finished), callable_mp(this, &AudioStreamImportSettingsDialog::_on_finished));
color_rect->add_child(_player);
VBoxContainer *vbox = memnew(VBoxContainer);
vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0);
color_rect->add_child(vbox);
vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
_preview = memnew(ColorRect);
_preview->set_v_size_flags(Control::SIZE_EXPAND_FILL);
_preview->connect(SceneStringName(draw), callable_mp(this, &AudioStreamImportSettingsDialog::_draw_preview));
_preview->set_v_size_flags(Control::SIZE_EXPAND_FILL);
vbox->add_child(_preview);
zoom_bar = memnew(HScrollBar);
zoom_bar->hide();
vbox->add_child(zoom_bar);
zoom_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
zoom_bar->connect(SceneStringName(value_changed), callable_mp(this, &AudioStreamImportSettingsDialog::_preview_zoom_offset_changed));
HBoxContainer *hbox = memnew(HBoxContainer);
hbox->add_theme_constant_override("separation", 0);
vbox->add_child(hbox);
_indicator = memnew(Control);
_indicator->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
_indicator->connect(SceneStringName(draw), callable_mp(this, &AudioStreamImportSettingsDialog::_draw_indicator));
_indicator->connect(SceneStringName(gui_input), callable_mp(this, &AudioStreamImportSettingsDialog::_on_input_indicator));
_indicator->connect(SceneStringName(mouse_exited), callable_mp(this, &AudioStreamImportSettingsDialog::_on_indicator_mouse_exited));
_preview->add_child(_indicator);
_play_button = memnew(Button);
_play_button->set_accessibility_name(TTRC("Play"));
_play_button->set_flat(true);
hbox->add_child(_play_button);
_play_button->connect(SceneStringName(pressed), callable_mp(this, &AudioStreamImportSettingsDialog::_play));
_stop_button = memnew(Button);
_stop_button->set_accessibility_name(TTRC("Stop"));
_stop_button->set_flat(true);
hbox->add_child(_stop_button);
_stop_button->connect(SceneStringName(pressed), callable_mp(this, &AudioStreamImportSettingsDialog::_stop));
_current_label = memnew(Label);
_current_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
_current_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
_current_label->set_modulate(Color(1, 1, 1, 0.5));
hbox->add_child(_current_label);
_duration_label = memnew(Label);
hbox->add_child(_duration_label);
zoom_in = memnew(Button);
zoom_in->set_accessibility_name(TTRC("Zoom In"));
zoom_in->set_flat(true);
zoom_reset = memnew(Button);
zoom_reset->set_accessibility_name(TTRC("Reset Zoom"));
zoom_reset->set_flat(true);
zoom_out = memnew(Button);
zoom_out->set_accessibility_name(TTRC("Zoom Out"));
zoom_out->set_flat(true);
hbox->add_child(zoom_out);
hbox->add_child(zoom_reset);
hbox->add_child(zoom_in);
zoom_in->connect(SceneStringName(pressed), callable_mp(this, &AudioStreamImportSettingsDialog::_preview_zoom_in));
zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &AudioStreamImportSettingsDialog::_preview_zoom_reset));
zoom_out->connect(SceneStringName(pressed), callable_mp(this, &AudioStreamImportSettingsDialog::_preview_zoom_out));
singleton = this;
}

View File

@@ -0,0 +1,113 @@
/**************************************************************************/
/* audio_stream_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. */
/**************************************************************************/
#pragma once
#include "editor/plugins/editor_plugin.h"
#include "scene/audio/audio_stream_player.h"
#include "scene/gui/color_rect.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/spin_box.h"
class CheckBox;
class AudioStreamImportSettingsDialog : public ConfirmationDialog {
GDCLASS(AudioStreamImportSettingsDialog, ConfirmationDialog);
CheckBox *bpm_enabled = nullptr;
SpinBox *bpm_edit = nullptr;
CheckBox *beats_enabled = nullptr;
SpinBox *beats_edit = nullptr;
Label *bar_beats_label = nullptr;
SpinBox *bar_beats_edit = nullptr;
CheckBox *loop = nullptr;
SpinBox *loop_offset = nullptr;
ColorRect *color_rect = nullptr;
Ref<AudioStream> stream;
AudioStreamPlayer *_player = nullptr;
ColorRect *_preview = nullptr;
Control *_indicator = nullptr;
Label *_current_label = nullptr;
Label *_duration_label = nullptr;
HScrollBar *zoom_bar = nullptr;
Button *zoom_in = nullptr;
Button *zoom_reset = nullptr;
Button *zoom_out = nullptr;
Button *_play_button = nullptr;
Button *_stop_button = nullptr;
bool updating_settings = false;
float _current = 0;
bool _dragging = false;
bool _beat_len_dragging = false;
bool _pausing = false;
int _hovering_beat = -1;
HashMap<StringName, Variant> params;
String importer;
String path;
void _audio_changed();
static AudioStreamImportSettingsDialog *singleton;
void _settings_changed();
void _reimport();
protected:
void _notification(int p_what);
void _preview_changed(ObjectID p_which);
void _preview_zoom_in();
void _preview_zoom_out();
void _preview_zoom_reset();
void _preview_zoom_offset_changed(double);
void _play();
void _stop();
void _on_finished();
void _draw_preview();
void _draw_indicator();
void _on_input_indicator(Ref<InputEvent> p_event);
void _seek_to(real_t p_x);
void _set_beat_len_to(real_t p_x);
void _on_indicator_mouse_exited();
int _get_beat_at_pos(real_t p_x);
public:
void edit(const String &p_path, const String &p_importer, const Ref<AudioStream> &p_stream);
static AudioStreamImportSettingsDialog *get_singleton() { return singleton; }
AudioStreamImportSettingsDialog();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
/**************************************************************************/
/* dynamic_font_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. */
/**************************************************************************/
#pragma once
#include "core/io/resource_importer.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/tab_container.h"
#include "scene/gui/text_edit.h"
#include "scene/gui/tree.h"
#include "scene/resources/font.h"
class DynamicFontImportSettingsDialog;
class DynamicFontImportSettingsData : public RefCounted {
GDCLASS(DynamicFontImportSettingsData, RefCounted)
friend class DynamicFontImportSettingsDialog;
HashMap<StringName, Variant> settings;
HashMap<StringName, Variant> defaults;
List<ResourceImporter::ImportOption> options;
DynamicFontImportSettingsDialog *owner = nullptr;
HashSet<char32_t> selected_chars;
HashSet<int32_t> selected_glyphs;
Ref<FontFile> fd;
public:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
Ref<FontFile> get_font() const;
};
class EditorFileDialog;
class EditorInspector;
class EditorLocaleDialog;
class DynamicFontImportSettingsDialog : public ConfirmationDialog {
GDCLASS(DynamicFontImportSettingsDialog, ConfirmationDialog)
friend class DynamicFontImportSettingsData;
enum ItemButton {
BUTTON_ADD_VAR,
BUTTON_REMOVE_VAR,
};
static DynamicFontImportSettingsDialog *singleton;
String base_path;
Ref<DynamicFontImportSettingsData> import_settings_data;
List<ResourceImporter::ImportOption> options_variations;
List<ResourceImporter::ImportOption> options_general;
bool is_pixel = false;
// Root layout
Label *label_warn = nullptr;
TabContainer *main_pages = nullptr;
// Page 1 layout: Rendering Options
Label *page1_description = nullptr;
Label *font_name_label = nullptr;
Label *font_preview_label = nullptr;
EditorInspector *inspector_general = nullptr;
void _main_prop_changed(const String &p_edited_property);
// Page 2 layout: Preload Configurations
Label *page2_description = nullptr;
Label *label_vars = nullptr;
Button *add_var = nullptr;
Tree *vars_list = nullptr;
TreeItem *vars_list_root = nullptr;
EditorInspector *inspector_vars = nullptr;
void _variation_add();
void _variation_selected();
void _variation_remove(Object *p_item, int p_column, int p_id, MouseButton p_button);
void _variation_changed(const String &p_edited_property);
void _variations_validate();
TabContainer *preload_pages = nullptr;
Label *label_glyphs = nullptr;
void _glyph_clear();
void _glyph_update_lbl();
// Page 2.0 layout: Translations
Label *page2_0_description = nullptr;
Tree *locale_tree = nullptr;
TreeItem *locale_root = nullptr;
Button *btn_fill_locales = nullptr;
void _locale_edited();
void _process_locales();
// Page 2.1 layout: Text to select glyphs
Label *page2_1_description = nullptr;
TextEdit *text_edit = nullptr;
EditorInspector *inspector_text = nullptr;
Button *btn_fill = nullptr;
List<ResourceImporter::ImportOption> options_text;
Ref<DynamicFontImportSettingsData> text_settings_data;
void _change_text_opts();
void _glyph_text_selected();
// Page 2.2 layout: Character map
Label *page2_2_description = nullptr;
Tree *glyph_table = nullptr;
Tree *glyph_tree = nullptr;
TreeItem *glyph_root = nullptr;
void _glyph_selected();
void _range_edited();
void _range_selected();
void _edit_range(int32_t p_start, int32_t p_end);
bool _char_update(int32_t p_char);
void _range_update(int32_t p_start, int32_t p_end);
// Common
void _add_glyph_range_item(int32_t p_start, int32_t p_end, const String &p_name);
Ref<FontFile> font_preview;
Ref<FontFile> font_main;
void _re_import();
String _pad_zeros(const String &p_hex) const;
protected:
void _notification(int p_what);
public:
void open_settings(const String &p_path);
static DynamicFontImportSettingsDialog *get_singleton();
DynamicFontImportSettingsDialog();
};

View File

@@ -0,0 +1,248 @@
/**************************************************************************/
/* editor_atlas_packer.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "editor_atlas_packer.h"
#include "core/math/geometry_2d.h"
#include "core/math/vector2.h"
#include "core/math/vector2i.h"
#include "scene/resources/bit_map.h"
void EditorAtlasPacker::chart_pack(Vector<Chart> &charts, int &r_width, int &r_height, int p_atlas_max_size, int p_cell_resolution) {
int divide_by = MIN(64, p_cell_resolution);
Vector<PlottedBitmap> bitmaps;
int max_w = 0;
for (int i = 0; i < charts.size(); i++) {
const Chart &chart = charts[i];
//generate aabb
Rect2i aabb;
int vertex_count = chart.vertices.size();
const Vector2 *vertices = chart.vertices.ptr();
for (int j = 0; j < vertex_count; j++) {
if (j == 0) {
aabb.position = vertices[j];
} else {
aabb.expand_to(vertices[j]);
}
}
Ref<BitMap> src_bitmap;
src_bitmap.instantiate();
src_bitmap->create((aabb.size + Vector2(divide_by - 1, divide_by - 1)) / divide_by);
int w = src_bitmap->get_size().width;
int h = src_bitmap->get_size().height;
//plot triangles, using divisor
for (int j = 0; j < chart.faces.size(); j++) {
Vector2i v[3];
for (int k = 0; k < 3; k++) {
Vector2 vtx = chart.vertices[chart.faces[j].vertex[k]];
vtx -= aabb.position;
vtx /= divide_by;
vtx = vtx.min(Vector2(w - 1, h - 1));
v[k] = vtx;
}
for (int k = 0; k < 3; k++) {
int l = k == 0 ? 2 : k - 1;
Vector<Point2i> points = Geometry2D::bresenham_line(v[k], v[l]);
for (Point2i point : points) {
src_bitmap->set_bitv(point, true);
}
}
}
//src_bitmap->convert_to_image()->save_png("bitmap" + itos(i) + ".png");
//grow by 1 for each side
int bmw = src_bitmap->get_size().width + 2;
int bmh = src_bitmap->get_size().height + 2;
int heights_size = -1;
bool transpose = false;
if (chart.can_transpose && bmh > bmw) {
heights_size = bmh;
transpose = true;
} else {
heights_size = bmw;
}
max_w = MAX(max_w, heights_size);
Vector<int> top_heights;
Vector<int> bottom_heights;
top_heights.resize(heights_size);
bottom_heights.resize(heights_size);
for (int x = 0; x < heights_size; x++) {
top_heights.write[x] = -1;
bottom_heights.write[x] = 0x7FFFFFFF;
}
for (int x = 0; x < bmw; x++) {
for (int y = 0; y < bmh; y++) {
bool found_pixel = false;
for (int lx = x - 1; lx < x + 2 && !found_pixel; lx++) {
for (int ly = y - 1; ly < y + 2 && !found_pixel; ly++) {
int px = lx - 1;
if (px < 0 || px >= w) {
continue;
}
int py = ly - 1;
if (py < 0 || py >= h) {
continue;
}
if (src_bitmap->get_bit(px, py)) {
found_pixel = true;
}
}
}
if (found_pixel) {
if (transpose) {
if (x > top_heights[y]) {
top_heights.write[y] = x;
}
if (x < bottom_heights[y]) {
bottom_heights.write[y] = x;
}
} else {
if (y > top_heights[x]) {
top_heights.write[x] = y;
}
if (y < bottom_heights[x]) {
bottom_heights.write[x] = y;
}
}
}
}
}
String row;
for (int j = 0; j < top_heights.size(); j++) {
row += "(" + itos(top_heights[j]) + "-" + itos(bottom_heights[j]) + "),";
}
PlottedBitmap plotted_bitmap;
plotted_bitmap.offset = aabb.position;
plotted_bitmap.top_heights = top_heights;
plotted_bitmap.bottom_heights = bottom_heights;
plotted_bitmap.chart_index = i;
plotted_bitmap.transposed = transpose;
plotted_bitmap.area = bmw * bmh;
bitmaps.push_back(plotted_bitmap);
}
bitmaps.sort();
int atlas_max_width = nearest_power_of_2_templated(p_atlas_max_size) / divide_by;
int atlas_w = nearest_power_of_2_templated(max_w);
int atlas_h;
while (true) {
atlas_h = 0;
//do a tetris
Vector<int> heights;
heights.resize(atlas_w);
for (int i = 0; i < atlas_w; i++) {
heights.write[i] = 0;
}
int *atlas_ptr = heights.ptrw();
for (int i = 0; i < bitmaps.size(); i++) {
int best_height = 0x7FFFFFFF;
int best_height_offset = -1;
int w = bitmaps[i].top_heights.size();
const int *top_heights = bitmaps[i].top_heights.ptr();
const int *bottom_heights = bitmaps[i].bottom_heights.ptr();
for (int j = 0; j <= atlas_w - w; j++) {
int height = 0;
for (int k = 0; k < w; k++) {
int pixmap_h = bottom_heights[k];
if (pixmap_h == 0x7FFFFFFF) {
continue; //no pixel here, anything is fine
}
int h = MAX(0, atlas_ptr[j + k] - pixmap_h);
if (h > height) {
height = h;
}
}
if (height < best_height) {
best_height = height;
best_height_offset = j;
}
}
for (int j = 0; j < w; j++) { //add
if (top_heights[j] == -1) { //unused
continue;
}
int height = best_height + top_heights[j] + 1;
atlas_ptr[j + best_height_offset] = height;
atlas_h = MAX(atlas_h, height);
}
// set
Vector2 offset = bitmaps[i].offset;
if (bitmaps[i].transposed) {
SWAP(offset.x, offset.y);
}
Vector2 final_pos = Vector2(best_height_offset * divide_by, best_height * divide_by) + Vector2(divide_by, divide_by) - offset;
charts.write[bitmaps[i].chart_index].final_offset = final_pos;
charts.write[bitmaps[i].chart_index].transposed = bitmaps[i].transposed;
}
if (atlas_h <= atlas_w * 2 || atlas_w >= atlas_max_width) {
break; //ok this one is enough
}
//try again
atlas_w *= 2;
}
r_width = atlas_w * divide_by;
r_height = atlas_h * divide_by;
}

View File

@@ -0,0 +1,69 @@
/**************************************************************************/
/* editor_atlas_packer.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. */
/**************************************************************************/
#pragma once
#include "core/math/vector2.h"
#include "core/math/vector2i.h"
#include "core/templates/vector.h"
class EditorAtlasPacker {
public:
struct Chart {
Vector<Vector2> vertices;
struct Face {
int vertex[3] = { 0 };
};
Vector<Face> faces;
bool can_transpose = false;
Vector2 final_offset;
bool transposed = false;
};
private:
struct PlottedBitmap {
int chart_index = 0;
Vector2i offset;
int area = 0;
Vector<int> top_heights;
Vector<int> bottom_heights;
bool transposed = false;
Vector2 final_pos;
bool operator<(const PlottedBitmap &p_bm) const {
return area > p_bm.area;
}
};
public:
static void chart_pack(Vector<Chart> &charts, int &r_width, int &r_height, int p_atlas_max_size = 2048, int p_cell_resolution = 4);
};

View File

@@ -0,0 +1,229 @@
/**************************************************************************/
/* editor_import_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "editor_import_plugin.h"
#include "core/object/script_language.h"
#include "editor/file_system/editor_file_system.h"
String EditorImportPlugin::get_importer_name() const {
String ret;
if (GDVIRTUAL_CALL(_get_importer_name, ret)) {
return ret;
}
ERR_FAIL_V_MSG(String(), "Unimplemented _get_importer_name in add-on.");
}
String EditorImportPlugin::get_visible_name() const {
String ret;
if (GDVIRTUAL_CALL(_get_visible_name, ret)) {
return ret;
}
ERR_FAIL_V_MSG(String(), "Unimplemented _get_visible_name in add-on.");
}
void EditorImportPlugin::get_recognized_extensions(List<String> *p_extensions) const {
Vector<String> extensions;
if (GDVIRTUAL_CALL(_get_recognized_extensions, extensions)) {
for (int i = 0; i < extensions.size(); i++) {
p_extensions->push_back(extensions[i]);
}
return;
}
ERR_FAIL_MSG("Unimplemented _get_recognized_extensions in add-on.");
}
String EditorImportPlugin::get_preset_name(int p_idx) const {
String ret;
if (GDVIRTUAL_CALL(_get_preset_name, p_idx, ret)) {
return ret;
}
ERR_FAIL_V_MSG(itos(p_idx), "Unimplemented _get_preset_name in add-on.");
}
int EditorImportPlugin::get_preset_count() const {
int ret;
if (GDVIRTUAL_CALL(_get_preset_count, ret)) {
return ret;
}
return 0;
}
String EditorImportPlugin::get_save_extension() const {
String ret;
if (GDVIRTUAL_CALL(_get_save_extension, ret)) {
return ret;
}
ERR_FAIL_V_MSG(String(), "Unimplemented _get_save_extension in add-on.");
}
String EditorImportPlugin::get_resource_type() const {
String ret;
if (GDVIRTUAL_CALL(_get_resource_type, ret)) {
return ret;
}
ERR_FAIL_V_MSG(String(), "Unimplemented _get_resource_type in add-on.");
}
float EditorImportPlugin::get_priority() const {
float ret;
if (GDVIRTUAL_CALL(_get_priority, ret)) {
return ret;
}
return 1.0;
}
int EditorImportPlugin::get_import_order() const {
int ret;
if (GDVIRTUAL_CALL(_get_import_order, ret)) {
return ret;
}
return IMPORT_ORDER_DEFAULT;
}
int EditorImportPlugin::get_format_version() const {
int ret = 0;
if (GDVIRTUAL_CALL(_get_format_version, ret)) {
return ret;
}
return 0;
}
void EditorImportPlugin::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options, int p_preset) const {
Array needed = { "name", "default_value" };
TypedArray<Dictionary> options;
if (GDVIRTUAL_CALL(_get_import_options, p_path, p_preset, options)) {
for (int i = 0; i < options.size(); i++) {
Dictionary d = options[i];
ERR_FAIL_COND(!d.has_all(needed));
String name = d["name"];
Variant default_value = d["default_value"];
PropertyHint hint = PROPERTY_HINT_NONE;
if (d.has("property_hint")) {
hint = (PropertyHint)d["property_hint"].operator int64_t();
}
String hint_string;
if (d.has("hint_string")) {
hint_string = d["hint_string"];
}
uint32_t usage = PROPERTY_USAGE_DEFAULT;
if (d.has("usage")) {
usage = d["usage"];
}
ImportOption option(PropertyInfo(default_value.get_type(), name, hint, hint_string, usage), default_value);
r_options->push_back(option);
}
return;
}
ERR_FAIL_MSG("Unimplemented _get_import_options in add-on.");
}
bool EditorImportPlugin::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
Dictionary d;
HashMap<StringName, Variant>::ConstIterator E = p_options.begin();
while (E) {
d[E->key] = E->value;
++E;
}
bool visible = false;
if (GDVIRTUAL_CALL(_get_option_visibility, p_path, p_option, d, visible)) {
return visible;
}
return true;
}
Error EditorImportPlugin::import(ResourceUID::ID p_source_id, 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) {
Dictionary options;
TypedArray<String> platform_variants, gen_files;
HashMap<StringName, Variant>::ConstIterator E = p_options.begin();
while (E) {
options[E->key] = E->value;
++E;
}
Error err = OK;
if (GDVIRTUAL_CALL(_import, p_source_file, p_save_path, options, platform_variants, gen_files, err)) {
for (int i = 0; i < platform_variants.size(); i++) {
r_platform_variants->push_back(platform_variants[i]);
}
for (int i = 0; i < gen_files.size(); i++) {
r_gen_files->push_back(gen_files[i]);
}
return err;
}
ERR_FAIL_V_MSG(ERR_METHOD_NOT_FOUND, "Unimplemented _import in add-on.");
}
bool EditorImportPlugin::can_import_threaded() const {
bool ret = false;
if (GDVIRTUAL_CALL(_can_import_threaded, ret)) {
return ret;
} else {
return ResourceImporter::can_import_threaded();
}
}
Error EditorImportPlugin::_append_import_external_resource(const String &p_file, const Dictionary &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) {
HashMap<StringName, Variant> options;
for (const KeyValue<Variant, Variant> &kv : p_custom_options) {
options.insert(kv.key, kv.value);
}
return append_import_external_resource(p_file, options, p_custom_importer, p_generator_parameters);
}
Error EditorImportPlugin::append_import_external_resource(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) {
ERR_FAIL_COND_V_MSG(!EditorFileSystem::get_singleton()->is_importing(), ERR_INVALID_PARAMETER, "Can only append files to import during a current reimport process.");
return EditorFileSystem::get_singleton()->reimport_append(p_file, p_custom_options, p_custom_importer, p_generator_parameters);
}
void EditorImportPlugin::_bind_methods() {
GDVIRTUAL_BIND(_get_importer_name)
GDVIRTUAL_BIND(_get_visible_name)
GDVIRTUAL_BIND(_get_preset_count)
GDVIRTUAL_BIND(_get_preset_name, "preset_index")
GDVIRTUAL_BIND(_get_recognized_extensions)
GDVIRTUAL_BIND(_get_import_options, "path", "preset_index")
GDVIRTUAL_BIND(_get_save_extension)
GDVIRTUAL_BIND(_get_resource_type)
GDVIRTUAL_BIND(_get_priority)
GDVIRTUAL_BIND(_get_import_order)
GDVIRTUAL_BIND(_get_format_version)
GDVIRTUAL_BIND(_get_option_visibility, "path", "option_name", "options")
GDVIRTUAL_BIND(_import, "source_file", "save_path", "options", "platform_variants", "gen_files");
GDVIRTUAL_BIND(_can_import_threaded);
ClassDB::bind_method(D_METHOD("append_import_external_resource", "path", "custom_options", "custom_importer", "generator_parameters"), &EditorImportPlugin::_append_import_external_resource, DEFVAL(Dictionary()), DEFVAL(String()), DEFVAL(Variant()));
}

View File

@@ -0,0 +1,75 @@
/**************************************************************************/
/* editor_import_plugin.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. */
/**************************************************************************/
#pragma once
#include "core/io/resource_importer.h"
#include "core/variant/typed_array.h"
class EditorImportPlugin : public ResourceImporter {
GDCLASS(EditorImportPlugin, ResourceImporter);
protected:
static void _bind_methods();
GDVIRTUAL0RC(String, _get_importer_name)
GDVIRTUAL0RC(String, _get_visible_name)
GDVIRTUAL0RC(int, _get_preset_count)
GDVIRTUAL1RC(String, _get_preset_name, int)
GDVIRTUAL0RC(Vector<String>, _get_recognized_extensions)
GDVIRTUAL2RC(TypedArray<Dictionary>, _get_import_options, String, int)
GDVIRTUAL0RC(String, _get_save_extension)
GDVIRTUAL0RC(String, _get_resource_type)
GDVIRTUAL0RC(float, _get_priority)
GDVIRTUAL0RC(int, _get_import_order)
GDVIRTUAL0RC(int, _get_format_version)
GDVIRTUAL3RC(bool, _get_option_visibility, String, StringName, Dictionary)
GDVIRTUAL5RC(Error, _import, String, String, Dictionary, TypedArray<String>, TypedArray<String>)
GDVIRTUAL0RC(bool, _can_import_threaded)
Error _append_import_external_resource(const String &p_file, const Dictionary &p_custom_options = Dictionary(), const String &p_custom_importer = String(), Variant p_generator_parameters = Variant());
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_preset_name(int p_idx) const override;
virtual int get_preset_count() const override;
virtual String get_save_extension() const override;
virtual String get_resource_type() const override;
virtual float get_priority() const override;
virtual int get_import_order() const override;
virtual int get_format_version() const override;
virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) 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(ResourceUID::ID p_source_id, 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 = nullptr) override;
virtual bool can_import_threaded() const override;
Error append_import_external_resource(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant p_generator_parameters = Variant());
};

View File

@@ -0,0 +1,185 @@
/**************************************************************************/
/* fbx_importer_manager.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "fbx_importer_manager.h"
#include "core/config/project_settings.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/link_button.h"
void FBXImporterManager::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
fbx_path_browse->set_button_icon(get_editor_theme_icon(SNAME("FileBrowse")));
} break;
case NOTIFICATION_READY: {
connect(SceneStringName(confirmed), callable_mp(this, &FBXImporterManager::_path_confirmed));
} break;
}
}
void FBXImporterManager::show_dialog(bool p_exclusive) {
String fbx2gltf_path = EDITOR_GET("filesystem/import/fbx/fbx2gltf_path");
fbx_path->set_text(fbx2gltf_path);
_validate_path(fbx2gltf_path);
// If exclusive, we're importing a FBX file, there's no exit.
is_importing = p_exclusive;
set_flag(Window::FLAG_BORDERLESS, p_exclusive); // Avoid closing accidentally.
set_close_on_escape(!p_exclusive);
if (is_importing) {
get_cancel_button()->set_text(TTR("Disable FBX2glTF & Restart"));
get_cancel_button()->set_tooltip_text(TTR("Canceling this dialog will disable the FBX2glTF importer and use the ufbx importer.\nYou can re-enable FBX2glTF in the Project Settings under Filesystem > Import > FBX > Enabled.\n\nThe editor will restart as importers are registered when the editor starts."));
} else {
get_cancel_button()->set_text(TTR("Cancel"));
get_cancel_button()->set_tooltip_text("");
}
popup_centered();
}
void FBXImporterManager::_validate_path(const String &p_path) {
String error;
bool success = false;
if (p_path == "") {
error = TTR("Path to FBX2glTF executable is empty.");
} else if (!FileAccess::exists(p_path)) {
error = TTR("Path to FBX2glTF executable is invalid.");
} else {
List<String> args;
args.push_back("--version");
int exitcode;
Error err = OS::get_singleton()->execute(p_path, args, nullptr, &exitcode);
if (err == OK && exitcode == 0) {
success = true;
} else {
error = TTR("Error executing this file (wrong version or architecture).");
}
}
if (success) {
path_status->set_text(TTR("FBX2glTF executable is valid."));
path_status->add_theme_color_override(SceneStringName(font_color), path_status->get_theme_color(SNAME("success_color"), EditorStringName(Editor)));
get_ok_button()->set_disabled(false);
} else {
path_status->set_text(error);
path_status->add_theme_color_override(SceneStringName(font_color), path_status->get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
get_ok_button()->set_disabled(true);
}
}
void FBXImporterManager::_select_file(const String &p_path) {
fbx_path->set_text(p_path);
_validate_path(p_path);
}
void FBXImporterManager::_path_confirmed() {
String path = fbx_path->get_text();
EditorSettings::get_singleton()->set("filesystem/import/fbx/fbx2gltf_path", path);
EditorSettings::get_singleton()->save();
}
void FBXImporterManager::_cancel_setup() {
if (!is_importing) {
return; // No worry.
}
// No escape.
ProjectSettings::get_singleton()->set("filesystem/import/fbx2gltf/enabled", false);
ProjectSettings::get_singleton()->save();
EditorNode::get_singleton()->save_all_scenes();
EditorNode::get_singleton()->restart_editor();
}
void FBXImporterManager::_browse_install() {
if (fbx_path->get_text() != String()) {
browse_dialog->set_current_file(fbx_path->get_text());
}
browse_dialog->popup_centered_ratio();
}
FBXImporterManager *FBXImporterManager::singleton = nullptr;
FBXImporterManager::FBXImporterManager() {
singleton = this;
set_title(TTR("Configure FBX Importer"));
VBoxContainer *vb = memnew(VBoxContainer);
vb->add_child(memnew(Label(TTR("FBX2glTF is required for importing FBX files if using FBX2glTF.\nAlternatively, you can use ufbx by disabling FBX2glTF.\nPlease download the necessary tool and provide a valid path to the binary:"))));
LinkButton *lb = memnew(LinkButton);
lb->set_text(TTR("Click this link to download FBX2glTF"));
lb->set_uri("https://godotengine.org/fbx-import");
vb->add_child(lb);
HBoxContainer *hb = memnew(HBoxContainer);
fbx_path = memnew(LineEdit);
fbx_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
fbx_path->set_accessibility_name(TTRC("Path"));
hb->add_child(fbx_path);
fbx_path_browse = memnew(Button);
fbx_path_browse->set_text(TTR("Browse"));
fbx_path_browse->connect(SceneStringName(pressed), callable_mp(this, &FBXImporterManager::_browse_install));
hb->add_child(fbx_path_browse);
hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
hb->set_custom_minimum_size(Size2(400 * EDSCALE, 0));
vb->add_child(hb);
path_status = memnew(Label);
path_status->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
vb->add_child(path_status);
add_child(vb);
fbx_path->connect(SceneStringName(text_changed), callable_mp(this, &FBXImporterManager::_validate_path));
get_ok_button()->set_text(TTR("Confirm Path"));
get_cancel_button()->connect(SceneStringName(pressed), callable_mp(this, &FBXImporterManager::_cancel_setup));
browse_dialog = memnew(EditorFileDialog);
browse_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
browse_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
#ifdef WINDOWS_ENABLED
browse_dialog->add_filter("*.exe");
#endif
browse_dialog->connect("file_selected", callable_mp(this, &FBXImporterManager::_select_file));
add_child(browse_dialog);
}

View File

@@ -0,0 +1,66 @@
/**************************************************************************/
/* fbx_importer_manager.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. */
/**************************************************************************/
#pragma once
#include "editor/gui/editor_file_dialog.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/line_edit.h"
class FBXImporterManager : public ConfirmationDialog {
GDCLASS(FBXImporterManager, ConfirmationDialog)
bool is_importing = false;
Label *message = nullptr;
LineEdit *fbx_path = nullptr;
Button *fbx_path_browse = nullptr;
EditorFileDialog *browse_dialog = nullptr;
Label *path_status = nullptr;
void _validate_path(const String &p_path);
void _select_file(const String &p_path);
void _path_confirmed();
void _cancel_setup();
void _browse_install();
void _link_clicked();
static FBXImporterManager *singleton;
protected:
void _notification(int p_what);
public:
static FBXImporterManager *get_singleton() { return singleton; }
void show_dialog(bool p_exclusive = false);
FBXImporterManager();
};

View File

@@ -0,0 +1,235 @@
/**************************************************************************/
/* import_defaults_editor.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 "import_defaults_editor.h"
#include "core/config/project_settings.h"
#include "core/io/resource_importer.h"
#include "editor/inspector/editor_inspector.h"
#include "editor/inspector/editor_sectioned_inspector.h"
#include "editor/settings/action_map_editor.h"
#include "scene/gui/center_container.h"
#include "scene/gui/label.h"
class ImportDefaultsEditorSettings : public Object {
GDCLASS(ImportDefaultsEditorSettings, Object)
friend class ImportDefaultsEditor;
List<PropertyInfo> properties;
HashMap<StringName, Variant> values;
HashMap<StringName, Variant> default_values;
Ref<ResourceImporter> importer;
protected:
bool _set(const StringName &p_name, const Variant &p_value) {
if (values.has(p_name)) {
values[p_name] = p_value;
return true;
} else {
return false;
}
}
bool _get(const StringName &p_name, Variant &r_ret) const {
if (values.has(p_name)) {
r_ret = values[p_name];
return true;
} else {
r_ret = Variant();
return false;
}
}
void _get_property_list(List<PropertyInfo> *p_list) const {
if (importer.is_null()) {
return;
}
for (const PropertyInfo &E : properties) {
if (importer->get_option_visibility("", E.name, values)) {
p_list->push_back(E);
}
}
}
};
void ImportDefaultsEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PREDELETE: {
inspector->edit(nullptr);
} break;
}
}
void ImportDefaultsEditor::_reset() {
if (settings->importer.is_valid()) {
settings->values = settings->default_values;
settings->notify_property_list_changed();
}
}
void ImportDefaultsEditor::_save() {
if (settings->importer.is_valid()) {
Dictionary modified;
for (const KeyValue<StringName, Variant> &E : settings->values) {
if (E.value != settings->default_values[E.key]) {
modified[E.key] = E.value;
}
}
if (modified.size()) {
ProjectSettings::get_singleton()->set("importer_defaults/" + settings->importer->get_importer_name(), modified);
} else {
ProjectSettings::get_singleton()->set("importer_defaults/" + settings->importer->get_importer_name(), Variant());
}
ProjectSettings::get_singleton()->save();
}
}
void ImportDefaultsEditor::_update_importer() {
List<Ref<ResourceImporter>> importer_list;
ResourceFormatImporter::get_singleton()->get_importers(&importer_list);
Ref<ResourceImporter> importer;
for (const Ref<ResourceImporter> &E : importer_list) {
if (E->get_visible_name() == importers->get_item_text(importers->get_selected())) {
importer = E;
break;
}
}
settings->properties.clear();
settings->values.clear();
settings->importer = importer;
if (importer.is_valid()) {
List<ResourceImporter::ImportOption> options;
importer->get_import_options("", &options);
Dictionary d;
if (ProjectSettings::get_singleton()->has_setting("importer_defaults/" + importer->get_importer_name())) {
d = GLOBAL_GET("importer_defaults/" + importer->get_importer_name());
}
for (const ResourceImporter::ImportOption &E : options) {
settings->properties.push_back(E.option);
if (d.has(E.option.name)) {
settings->values[E.option.name] = d[E.option.name];
} else {
settings->values[E.option.name] = E.default_value;
}
settings->default_values[E.option.name] = E.default_value;
}
save_defaults->set_disabled(false);
reset_defaults->set_disabled(false);
} else {
save_defaults->set_disabled(true);
reset_defaults->set_disabled(true);
}
settings->notify_property_list_changed();
// Set the importer class to fetch the correct class in the XML class reference.
// This allows tooltips to display when hovering properties.
inspector->set_object_class(importer->get_class_name());
inspector->edit(settings);
}
void ImportDefaultsEditor::_importer_selected(int p_index) {
_update_importer();
}
void ImportDefaultsEditor::clear() {
String last_selected;
if (importers->get_selected() >= 0) {
last_selected = importers->get_item_text(importers->get_selected());
}
importers->clear();
List<Ref<ResourceImporter>> importer_list;
ResourceFormatImporter::get_singleton()->get_importers(&importer_list);
Vector<String> names;
for (const Ref<ResourceImporter> &E : importer_list) {
String vn = E->get_visible_name();
names.push_back(vn);
}
names.sort();
// `last_selected.is_empty()` means it's the first time being called.
if (last_selected.is_empty() && !names.is_empty()) {
last_selected = names[0];
}
for (int i = 0; i < names.size(); i++) {
importers->add_item(names[i]);
if (names[i] == last_selected) {
importers->select(i);
_update_importer();
}
}
}
ImportDefaultsEditor::ImportDefaultsEditor() {
ProjectSettings::get_singleton()->add_hidden_prefix("importer_defaults/");
HBoxContainer *hb = memnew(HBoxContainer);
hb->add_child(memnew(Label(TTRC("Importer:"))));
importers = memnew(OptionButton);
hb->add_child(importers);
hb->add_spacer();
importers->connect(SceneStringName(item_selected), callable_mp(this, &ImportDefaultsEditor::_importer_selected));
reset_defaults = memnew(Button);
reset_defaults->set_text(TTRC("Reset to Defaults"));
reset_defaults->set_disabled(true);
reset_defaults->connect(SceneStringName(pressed), callable_mp(this, &ImportDefaultsEditor::_reset));
hb->add_child(reset_defaults);
add_child(hb);
inspector = memnew(EditorInspector);
add_child(inspector);
inspector->set_v_size_flags(SIZE_EXPAND_FILL);
// Make it possible to display tooltips stored in the XML class reference.
// The object name is set when the importer changes in `_update_importer()`.
inspector->set_use_doc_hints(true);
CenterContainer *cc = memnew(CenterContainer);
save_defaults = memnew(Button);
save_defaults->set_text(TTRC("Save"));
save_defaults->connect(SceneStringName(pressed), callable_mp(this, &ImportDefaultsEditor::_save));
cc->add_child(save_defaults);
add_child(cc);
settings = memnew(ImportDefaultsEditorSettings);
}
ImportDefaultsEditor::~ImportDefaultsEditor() {
memdelete(settings);
}

View File

@@ -0,0 +1,65 @@
/**************************************************************************/
/* import_defaults_editor.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. */
/**************************************************************************/
#pragma once
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/option_button.h"
class ImportDefaultsEditorSettings;
class EditorInspector;
class ImportDefaultsEditor : public VBoxContainer {
GDCLASS(ImportDefaultsEditor, VBoxContainer)
OptionButton *importers = nullptr;
Button *save_defaults = nullptr;
Button *reset_defaults = nullptr;
EditorInspector *inspector = nullptr;
ImportDefaultsEditorSettings *settings = nullptr;
void _update_importer();
void _importer_selected(int p_index);
void _reset();
void _save();
protected:
void _notification(int p_what);
public:
void clear();
ImportDefaultsEditor();
~ImportDefaultsEditor();
};

View File

@@ -0,0 +1,107 @@
/**************************************************************************/
/* resource_importer_bitmask.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_bitmask.h"
#include "core/io/image.h"
#include "core/io/image_loader.h"
#include "core/io/resource_saver.h"
#include "scene/resources/bit_map.h"
String ResourceImporterBitMap::get_importer_name() const {
return "bitmap";
}
String ResourceImporterBitMap::get_visible_name() const {
return "BitMap";
}
void ResourceImporterBitMap::get_recognized_extensions(List<String> *p_extensions) const {
ImageLoader::get_recognized_extensions(p_extensions);
}
String ResourceImporterBitMap::get_save_extension() const {
return "res";
}
String ResourceImporterBitMap::get_resource_type() const {
return "BitMap";
}
bool ResourceImporterBitMap::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
return true;
}
int ResourceImporterBitMap::get_preset_count() const {
return 0;
}
String ResourceImporterBitMap::get_preset_name(int p_idx) const {
return String();
}
void ResourceImporterBitMap::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "create_from", PROPERTY_HINT_ENUM, "Black & White,Alpha"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.5));
}
Error ResourceImporterBitMap::import(ResourceUID::ID p_source_id, 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) {
int create_from = p_options["create_from"];
float threshold = p_options["threshold"];
Ref<Image> image;
image.instantiate();
Error err = ImageLoader::load_image(p_source_file, image);
if (err != OK) {
return err;
}
int w = image->get_width();
int h = image->get_height();
Ref<BitMap> bitmap;
bitmap.instantiate();
bitmap->create(Size2(w, h));
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
bool bit;
Color c = image->get_pixel(j, i);
if (create_from == 0) { //b&W
bit = c.get_v() > threshold;
} else {
bit = c.a > threshold;
}
bitmap->set_bit(j, i, bit);
}
}
return ResourceSaver::save(bitmap, p_save_path + ".res");
}

View File

@@ -0,0 +1,53 @@
/**************************************************************************/
/* resource_importer_bitmask.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. */
/**************************************************************************/
#pragma once
#include "core/io/resource_importer.h"
class ResourceImporterBitMap : public ResourceImporter {
GDCLASS(ResourceImporterBitMap, 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_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(ResourceUID::ID p_source_id, 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 can_import_threaded() const override { return true; }
};

View File

@@ -0,0 +1,111 @@
/**************************************************************************/
/* resource_importer_bmfont.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_bmfont.h"
#include "core/io/config_file.h"
#include "core/io/resource_saver.h"
String ResourceImporterBMFont::get_importer_name() const {
return "font_data_bmfont";
}
String ResourceImporterBMFont::get_visible_name() const {
return "Font Data (AngelCode BMFont)";
}
void ResourceImporterBMFont::get_recognized_extensions(List<String> *p_extensions) const {
if (p_extensions) {
p_extensions->push_back("font");
p_extensions->push_back("fnt");
}
}
String ResourceImporterBMFont::get_save_extension() const {
return "fontdata";
}
String ResourceImporterBMFont::get_resource_type() const {
return "FontFile";
}
bool ResourceImporterBMFont::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
return true;
}
void ResourceImporterBMFont::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::ARRAY, "fallbacks", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("Font")), Array()));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "scaling_mode", PROPERTY_HINT_ENUM, "Disabled,Enabled (Integer),Enabled (Fractional)"), TextServer::FIXED_SIZE_SCALE_ENABLED));
}
Error ResourceImporterBMFont::import(ResourceUID::ID p_source_id, 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) {
print_verbose("Importing BMFont font from: " + p_source_file);
Array fallbacks = p_options["fallbacks"];
TextServer::FixedSizeScaleMode smode = (TextServer::FixedSizeScaleMode)p_options["scaling_mode"].operator int();
Ref<FontFile> font;
font.instantiate();
List<String> image_files;
Error err = font->_load_bitmap_font(p_source_file, &image_files);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load font to file \"" + p_source_file + "\".");
// Update import settings for the image files used by font.
for (const String &file : image_files) {
Ref<ConfigFile> config;
config.instantiate();
err = config->load(file + ".import");
if (err == OK) {
config->clear();
config->set_value("remap", "importer", "skip");
config->save(file + ".import");
}
}
font->set_allow_system_fallback(false);
font->set_fallbacks(fallbacks);
font->set_fixed_size_scale_mode(smode);
int flg = 0;
if ((bool)p_options["compress"]) {
flg |= ResourceSaver::SaverFlags::FLAG_COMPRESS;
}
print_verbose("Saving to: " + p_save_path + ".fontdata");
err = ResourceSaver::save(font, p_save_path + ".fontdata", flg);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save font to file \"" + p_save_path + ".res\".");
print_verbose("Done saving to: " + p_save_path + ".fontdata");
return OK;
}

View File

@@ -0,0 +1,53 @@
/**************************************************************************/
/* resource_importer_bmfont.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. */
/**************************************************************************/
#pragma once
#include "core/io/resource_importer.h"
#include "scene/resources/font.h"
#include "servers/text_server.h"
class ResourceImporterBMFont : public ResourceImporter {
GDCLASS(ResourceImporterBMFont, 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 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(ResourceUID::ID p_source_id, 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 can_import_threaded() const override { return true; }
};

View File

@@ -0,0 +1,164 @@
/**************************************************************************/
/* resource_importer_csv_translation.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_csv_translation.h"
#include "core/io/file_access.h"
#include "core/io/resource_saver.h"
#include "core/string/optimized_translation.h"
#include "core/string/translation_server.h"
String ResourceImporterCSVTranslation::get_importer_name() const {
return "csv_translation";
}
String ResourceImporterCSVTranslation::get_visible_name() const {
return "CSV Translation";
}
void ResourceImporterCSVTranslation::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("csv");
}
String ResourceImporterCSVTranslation::get_save_extension() const {
return ""; //does not save a single resource
}
String ResourceImporterCSVTranslation::get_resource_type() const {
return "Translation";
}
bool ResourceImporterCSVTranslation::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
return true;
}
int ResourceImporterCSVTranslation::get_preset_count() const {
return 0;
}
String ResourceImporterCSVTranslation::get_preset_name(int p_idx) const {
return "";
}
void ResourceImporterCSVTranslation::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "delimiter", PROPERTY_HINT_ENUM, "Comma,Semicolon,Tab"), 0));
}
Error ResourceImporterCSVTranslation::import(ResourceUID::ID p_source_id, 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) {
bool compress = p_options["compress"];
String delimiter;
switch ((int)p_options["delimiter"]) {
case 0:
delimiter = ",";
break;
case 1:
delimiter = ";";
break;
case 2:
delimiter = "\t";
break;
}
Ref<FileAccess> f = FileAccess::open(p_source_file, FileAccess::READ);
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_INVALID_PARAMETER, "Cannot open file from path '" + p_source_file + "'.");
Vector<String> line = f->get_csv_line(delimiter);
ERR_FAIL_COND_V(line.size() <= 1, ERR_PARSE_ERROR);
Vector<String> locales;
Vector<Ref<Translation>> translations;
HashSet<int> skipped_locales;
for (int i = 1; i < line.size(); i++) {
String locale = TranslationServer::get_singleton()->standardize_locale(line[i]);
if (line[i].left(1) == "_") {
skipped_locales.insert(i);
continue;
} else if (locale.is_empty()) {
skipped_locales.insert(i);
ERR_CONTINUE_MSG(true, vformat("Error importing CSV translation: Invalid locale format '%s', should be 'language_Script_COUNTRY_VARIANT@extra'. This column will be ignored.", line[i]));
}
locales.push_back(locale);
Ref<Translation> translation;
translation.instantiate();
translation->set_locale(locale);
translations.push_back(translation);
}
do {
line = f->get_csv_line(delimiter);
String key = line[0];
if (!key.is_empty()) {
ERR_CONTINUE_MSG(line.size() != locales.size() + (int)skipped_locales.size() + 1, vformat("Error importing CSV translation: expected %d locale(s), but the '%s' key has %d locale(s).", locales.size(), key, line.size() - 1));
int write_index = 0; // Keep track of translations written in case some locales are skipped.
for (int i = 1; i < line.size(); i++) {
if (skipped_locales.has(i)) {
continue;
}
translations.write[write_index++]->add_message(key, line[i].c_unescape());
}
}
} while (!f->eof_reached());
for (int i = 0; i < translations.size(); i++) {
Ref<Translation> xlt = translations[i];
if (compress) {
Ref<OptimizedTranslation> cxl = memnew(OptimizedTranslation);
cxl->generate(xlt);
xlt = cxl;
}
String save_path = p_source_file.get_basename() + "." + translations[i]->get_locale() + ".translation";
ResourceUID::ID save_id = hash64_murmur3_64(translations[i]->get_locale().hash64(), p_source_id) & 0x7FFFFFFFFFFFFFFF;
bool uid_already_exists = ResourceUID::get_singleton()->has_id(save_id);
if (uid_already_exists) {
// Avoid creating a new file with a duplicate UID.
// Always use this UID, even if the user has moved it to a different path.
save_path = ResourceUID::get_singleton()->get_id_path(save_id);
}
ResourceSaver::save(xlt, save_path);
if (r_gen_files) {
r_gen_files->push_back(save_path);
}
if (!uid_already_exists) {
// No need to call set_uid if save_path already refers to save_id.
ResourceSaver::set_uid(save_path, save_id);
}
}
return OK;
}

View File

@@ -0,0 +1,54 @@
/**************************************************************************/
/* resource_importer_csv_translation.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. */
/**************************************************************************/
#pragma once
#include "core/io/resource_importer.h"
class ResourceImporterCSVTranslation : public ResourceImporter {
GDCLASS(ResourceImporterCSVTranslation, 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_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(ResourceUID::ID p_source_id, 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 can_import_threaded() const override { return true; }
};

View File

@@ -0,0 +1,283 @@
/**************************************************************************/
/* resource_importer_dynamic_font.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_dynamic_font.h"
#include "core/io/file_access.h"
#include "core/io/resource_saver.h"
#include "editor/import/dynamic_font_import_settings.h"
#include "scene/resources/font.h"
#include "servers/text_server.h"
String ResourceImporterDynamicFont::get_importer_name() const {
return "font_data_dynamic";
}
String ResourceImporterDynamicFont::get_visible_name() const {
return "Font Data (Dynamic Font)";
}
void ResourceImporterDynamicFont::get_recognized_extensions(List<String> *p_extensions) const {
if (p_extensions) {
p_extensions->push_back("ttf");
p_extensions->push_back("ttc");
p_extensions->push_back("otf");
p_extensions->push_back("otc");
p_extensions->push_back("woff");
p_extensions->push_back("woff2");
p_extensions->push_back("pfb");
p_extensions->push_back("pfm");
}
}
String ResourceImporterDynamicFont::get_save_extension() const {
return "fontdata";
}
String ResourceImporterDynamicFont::get_resource_type() const {
return "FontFile";
}
void ResourceImporterDynamicFont::get_build_dependencies(const String &p_path, HashSet<String> *r_dependencies) {
Ref<FontFile> font = ResourceLoader::load(p_path);
if (font.is_valid() && font->is_multichannel_signed_distance_field()) {
r_dependencies->insert("module_msdfgen_enabled");
}
}
bool ResourceImporterDynamicFont::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
if (p_option == "msdf_pixel_range" && !bool(p_options["multichannel_signed_distance_field"])) {
return false;
}
if (p_option == "msdf_size" && !bool(p_options["multichannel_signed_distance_field"])) {
return false;
}
if (p_option == "antialiasing" && bool(p_options["multichannel_signed_distance_field"])) {
return false;
}
if (p_option == "oversampling" && bool(p_options["multichannel_signed_distance_field"])) {
return false;
}
if (p_option == "subpixel_positioning" && bool(p_options["multichannel_signed_distance_field"])) {
return false;
}
if (p_option == "keep_rounding_remainders" && bool(p_options["multichannel_signed_distance_field"])) {
return false;
}
return true;
}
int ResourceImporterDynamicFont::get_preset_count() const {
return PRESET_MAX;
}
String ResourceImporterDynamicFont::get_preset_name(int p_idx) const {
switch (p_idx) {
case PRESET_DYNAMIC:
return TTR("Dynamically rendered TrueType/OpenType font");
case PRESET_MSDF:
return TTR("Prerendered multichannel(+true) signed distance field");
default:
return String();
}
}
void ResourceImporterDynamicFont::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
bool msdf = p_preset == PRESET_MSDF;
r_options->push_back(ImportOption(PropertyInfo(Variant::NIL, "Rendering", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP), Variant()));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD Subpixel"), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_mipmaps"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "disable_embedded_bitmaps"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), (msdf) ? true : false));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_RANGE, "1,100,1"), 8));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_RANGE, "1,250,1"), 48));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "allow_system_fallback"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "modulate_color_glyphs"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel,Auto (Except Pixel Fonts)"), 4));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "keep_rounding_remainders"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), 0.0));
r_options->push_back(ImportOption(PropertyInfo(Variant::NIL, "Fallbacks", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP), Variant()));
r_options->push_back(ImportOption(PropertyInfo(Variant::ARRAY, "fallbacks", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("Font")), Array()));
r_options->push_back(ImportOption(PropertyInfo(Variant::NIL, "Compress", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP), Variant()));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
// Hide from the main UI, only for advanced import dialog.
r_options->push_back(ImportOption(PropertyInfo(Variant::ARRAY, "preload", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), Array()));
r_options->push_back(ImportOption(PropertyInfo(Variant::DICTIONARY, "language_support", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), Dictionary()));
r_options->push_back(ImportOption(PropertyInfo(Variant::DICTIONARY, "script_support", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), Dictionary()));
r_options->push_back(ImportOption(PropertyInfo(Variant::DICTIONARY, "opentype_features", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), Dictionary()));
}
bool ResourceImporterDynamicFont::has_advanced_options() const {
return true;
}
void ResourceImporterDynamicFont::show_advanced_options(const String &p_path) {
DynamicFontImportSettingsDialog::get_singleton()->open_settings(p_path);
}
Error ResourceImporterDynamicFont::import(ResourceUID::ID p_source_id, 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) {
print_verbose("Importing dynamic font from: " + p_source_file);
int antialiasing = p_options["antialiasing"];
bool generate_mipmaps = p_options["generate_mipmaps"];
bool disable_embedded_bitmaps = p_options["disable_embedded_bitmaps"];
bool msdf = p_options["multichannel_signed_distance_field"];
int px_range = p_options["msdf_pixel_range"];
int px_size = p_options["msdf_size"];
Dictionary ot_ov = p_options["opentype_features"];
bool autohinter = p_options["force_autohinter"];
bool modulate_color_glyphs = p_options["modulate_color_glyphs"];
bool allow_system_fallback = p_options["allow_system_fallback"];
int hinting = p_options["hinting"];
int subpixel_positioning = p_options["subpixel_positioning"];
bool keep_rounding_remainders = p_options["keep_rounding_remainders"];
real_t oversampling = p_options["oversampling"];
Array fallbacks = p_options["fallbacks"];
// Load base font data.
Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_source_file);
// Create font.
Ref<FontFile> font;
font.instantiate();
font->set_data(data);
font->set_antialiasing((TextServer::FontAntialiasing)antialiasing);
font->set_disable_embedded_bitmaps(disable_embedded_bitmaps);
font->set_generate_mipmaps(generate_mipmaps);
font->set_multichannel_signed_distance_field(msdf);
font->set_msdf_pixel_range(px_range);
font->set_msdf_size(px_size);
font->set_opentype_feature_overrides(ot_ov);
font->set_fixed_size(0);
font->set_force_autohinter(autohinter);
font->set_modulate_color_glyphs(modulate_color_glyphs);
font->set_allow_system_fallback(allow_system_fallback);
font->set_hinting((TextServer::Hinting)hinting);
font->set_oversampling(oversampling);
font->set_fallbacks(fallbacks);
if (subpixel_positioning == 4 /* Auto (Except Pixel Fonts) */) {
Array rids = font->get_rids();
if (!rids.is_empty()) {
PackedInt32Array glyphs = TS->font_get_supported_glyphs(rids[0]);
bool is_pixel = true;
for (int32_t gl : glyphs) {
Dictionary ct = TS->font_get_glyph_contours(rids[0], 16, gl);
PackedInt32Array contours = ct["contours"];
PackedVector3Array points = ct["points"];
int prev_start = 0;
for (int i = 0; i < contours.size(); i++) {
for (int j = prev_start; j <= contours[i]; j++) {
int next_point = (j < contours[i]) ? (j + 1) : prev_start;
if ((points[j].z != (real_t)TextServer::CONTOUR_CURVE_TAG_ON) || (!Math::is_equal_approx(points[j].x, points[next_point].x) && !Math::is_equal_approx(points[j].y, points[next_point].y))) {
is_pixel = false;
break;
}
}
prev_start = contours[i] + 1;
if (!is_pixel) {
break;
}
}
if (!is_pixel) {
break;
}
}
if (is_pixel && !glyphs.is_empty()) {
print_line(vformat("%s: Pixel font detected, disabling subpixel positioning.", p_source_file));
subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_DISABLED;
} else {
subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO;
}
}
}
font->set_subpixel_positioning((TextServer::SubpixelPositioning)subpixel_positioning);
font->set_keep_rounding_remainders(keep_rounding_remainders);
Dictionary langs = p_options["language_support"];
for (const KeyValue<Variant, Variant> &kv : langs) {
String key = kv.key;
bool enabled = kv.value;
font->set_language_support_override(key, enabled);
}
Dictionary scripts = p_options["script_support"];
for (const KeyValue<Variant, Variant> &kv : scripts) {
String key = kv.key;
bool enabled = kv.value;
font->set_script_support_override(key, enabled);
}
Array preload_configurations = p_options["preload"];
for (int i = 0; i < preload_configurations.size(); i++) {
Dictionary preload_config = preload_configurations[i];
Dictionary variation = preload_config.has("variation_opentype") ? preload_config["variation_opentype"].operator Dictionary() : Dictionary();
double embolden = preload_config.has("variation_embolden") ? preload_config["variation_embolden"].operator double() : 0;
int face_index = preload_config.has("variation_face_index") ? preload_config["variation_face_index"].operator int() : 0;
Transform2D transform = preload_config.has("variation_transform") ? preload_config["variation_transform"].operator Transform2D() : Transform2D();
Vector2i size = preload_config.has("size") ? preload_config["size"].operator Vector2i() : Vector2i(16, 0);
String name = preload_config.has("name") ? preload_config["name"].operator String() : vformat("Configuration %d", i);
RID conf_rid = font->find_variation(variation, face_index, embolden, transform);
Array chars = preload_config["chars"];
for (int j = 0; j < chars.size(); j++) {
char32_t c = chars[j].operator int();
TS->font_render_range(conf_rid, size, c, c);
}
Array glyphs = preload_config["glyphs"];
for (int j = 0; j < glyphs.size(); j++) {
int32_t c = glyphs[j];
TS->font_render_glyph(conf_rid, size, c);
}
}
int flg = 0;
if ((bool)p_options["compress"]) {
flg |= ResourceSaver::SaverFlags::FLAG_COMPRESS;
}
print_verbose("Saving to: " + p_save_path + ".fontdata");
Error err = ResourceSaver::save(font, p_save_path + ".fontdata", flg);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save font to file \"" + p_save_path + ".res\".");
print_verbose("Done saving to: " + p_save_path + ".fontdata");
return OK;
}

View File

@@ -0,0 +1,64 @@
/**************************************************************************/
/* resource_importer_dynamic_font.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. */
/**************************************************************************/
#pragma once
#include "core/io/resource_importer.h"
class ResourceImporterDynamicFont : public ResourceImporter {
GDCLASS(ResourceImporterDynamicFont, ResourceImporter);
enum Presets {
PRESET_DYNAMIC,
PRESET_MSDF,
PRESET_MAX
};
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 void get_build_dependencies(const String &p_path, HashSet<String> *r_build_dependencies) 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;
bool has_advanced_options() const override;
void show_advanced_options(const String &p_path) override;
virtual Error import(ResourceUID::ID p_source_id, 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 can_import_threaded() const override { return true; }
};

View File

@@ -0,0 +1,94 @@
/**************************************************************************/
/* resource_importer_image.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_image.h"
#include "core/io/file_access.h"
#include "core/io/image_loader.h"
String ResourceImporterImage::get_importer_name() const {
return "image";
}
String ResourceImporterImage::get_visible_name() const {
return "Image";
}
void ResourceImporterImage::get_recognized_extensions(List<String> *p_extensions) const {
ImageLoader::get_recognized_extensions(p_extensions);
}
String ResourceImporterImage::get_save_extension() const {
return "image";
}
String ResourceImporterImage::get_resource_type() const {
return "Image";
}
bool ResourceImporterImage::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
return true;
}
int ResourceImporterImage::get_preset_count() const {
return 0;
}
String ResourceImporterImage::get_preset_name(int p_idx) const {
return String();
}
void ResourceImporterImage::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
}
Error ResourceImporterImage::import(ResourceUID::ID p_source_id, 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) {
Ref<FileAccess> f = FileAccess::open(p_source_file, FileAccess::READ);
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Cannot open file from path '" + p_source_file + "'.");
uint64_t len = f->get_length();
Vector<uint8_t> data;
data.resize(len);
f->get_buffer(data.ptrw(), len);
f = FileAccess::open(p_save_path + ".image", FileAccess::WRITE);
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, "Cannot create file in path '" + p_save_path + ".image'.");
//save the header GDIM
const uint8_t header[4] = { 'G', 'D', 'I', 'M' };
f->store_buffer(header, 4);
//SAVE the extension (so it can be recognized by the loader later
f->store_pascal_string(p_source_file.get_extension().to_lower());
//SAVE the actual image
f->store_buffer(data.ptr(), len);
return OK;
}

View File

@@ -0,0 +1,55 @@
/**************************************************************************/
/* resource_importer_image.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. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
#include "core/io/resource_importer.h"
class ResourceImporterImage : public ResourceImporter {
GDCLASS(ResourceImporterImage, 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_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(ResourceUID::ID p_source_id, 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 can_import_threaded() const override { return true; }
};

View File

@@ -0,0 +1,381 @@
/**************************************************************************/
/* resource_importer_imagefont.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_imagefont.h"
#include "core/io/image_loader.h"
#include "core/io/resource_saver.h"
String ResourceImporterImageFont::get_importer_name() const {
return "font_data_image";
}
String ResourceImporterImageFont::get_visible_name() const {
return "Font Data (Image Font)";
}
void ResourceImporterImageFont::get_recognized_extensions(List<String> *p_extensions) const {
if (p_extensions) {
ImageLoader::get_recognized_extensions(p_extensions);
}
}
String ResourceImporterImageFont::get_save_extension() const {
return "fontdata";
}
String ResourceImporterImageFont::get_resource_type() const {
return "FontFile";
}
bool ResourceImporterImageFont::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
return true;
}
void ResourceImporterImageFont::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "character_ranges"), Vector<String>()));
r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "kerning_pairs"), Vector<String>()));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "columns", PROPERTY_HINT_RANGE, "1,1024,1,or_greater"), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "rows", PROPERTY_HINT_RANGE, "1,1024,1,or_greater"), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::RECT2I, "image_margin"), Rect2i()));
r_options->push_back(ImportOption(PropertyInfo(Variant::RECT2I, "character_margin"), Rect2i()));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "ascent"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "descent"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::ARRAY, "fallbacks", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("Font")), Array()));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "scaling_mode", PROPERTY_HINT_ENUM, "Disabled,Enabled (Integer),Enabled (Fractional)"), TextServer::FIXED_SIZE_SCALE_ENABLED));
}
Error ResourceImporterImageFont::import(ResourceUID::ID p_source_id, 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) {
print_verbose("Importing image font from: " + p_source_file);
int columns = p_options["columns"];
int rows = p_options["rows"];
int ascent = p_options["ascent"];
int descent = p_options["descent"];
Vector<String> ranges = p_options["character_ranges"];
Vector<String> kern = p_options["kerning_pairs"];
Array fallbacks = p_options["fallbacks"];
Rect2i img_margin = p_options["image_margin"];
Rect2i char_margin = p_options["character_margin"];
TextServer::FixedSizeScaleMode smode = (TextServer::FixedSizeScaleMode)p_options["scaling_mode"].operator int();
Ref<Image> img;
img.instantiate();
Error err = ImageLoader::load_image(p_source_file, img);
ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, vformat("Can't load font texture: \"%s\".", p_source_file));
ERR_FAIL_COND_V_MSG(columns <= 0, ERR_FILE_CANT_READ, vformat("Columns (%d) must be positive.", columns));
ERR_FAIL_COND_V_MSG(rows <= 0, ERR_FILE_CANT_READ, vformat("Rows (%d) must be positive.", rows));
int remaining = columns * rows;
int chr_cell_width = (img->get_width() - img_margin.position.x - img_margin.size.x) / columns;
int chr_cell_height = (img->get_height() - img_margin.position.y - img_margin.size.y) / rows;
ERR_FAIL_COND_V_MSG(chr_cell_width <= 0 || chr_cell_height <= 0, ERR_FILE_CANT_READ, "Image margin too big.");
int chr_width = chr_cell_width - char_margin.position.x - char_margin.size.x;
int chr_height = chr_cell_height - char_margin.position.y - char_margin.size.y;
ERR_FAIL_COND_V_MSG(chr_width <= 0 || chr_height <= 0, ERR_FILE_CANT_READ, "Character margin too big.");
Ref<FontFile> font;
font.instantiate();
font->set_antialiasing(TextServer::FONT_ANTIALIASING_NONE);
font->set_generate_mipmaps(false);
font->set_multichannel_signed_distance_field(false);
font->set_fixed_size(chr_height);
font->set_subpixel_positioning(TextServer::SUBPIXEL_POSITIONING_DISABLED);
font->set_keep_rounding_remainders(true);
font->set_force_autohinter(false);
font->set_modulate_color_glyphs(false);
font->set_allow_system_fallback(false);
font->set_hinting(TextServer::HINTING_NONE);
font->set_fallbacks(fallbacks);
font->set_texture_image(0, Vector2i(chr_height, 0), 0, img);
font->set_fixed_size_scale_mode(smode);
int32_t pos = 0;
for (const String &range : ranges) {
Vector<int32_t> list;
int32_t start = -1;
int32_t end = -1;
int chr_adv = 0;
Vector2i chr_off;
{
enum RangeParseStep {
STEP_START_BEGIN,
STEP_START_READ_HEX,
STEP_START_READ_DEC,
STEP_END_BEGIN,
STEP_END_READ_HEX,
STEP_END_READ_DEC,
STEP_ADVANCE_BEGIN,
STEP_OFF_X_BEGIN,
STEP_OFF_Y_BEGIN,
STEP_FINISHED,
};
RangeParseStep step = STEP_START_BEGIN;
String token;
for (int c = 0; c < range.length(); c++) {
switch (step) {
case STEP_START_BEGIN:
case STEP_END_BEGIN: {
// Read range start/end first symbol.
if (range[c] == 'U' || range[c] == 'u') {
if ((c <= range.length() - 2) && range[c + 1] == '+') {
token = String();
if (step == STEP_START_BEGIN) {
step = STEP_START_READ_HEX;
} else {
step = STEP_END_READ_HEX;
}
c++; // Skip "+".
continue;
}
} else if (range[c] == '0' && (c <= range.length() - 2) && (range[c + 1] == 'x' || range[c + 1] == 'X')) {
// Read hexadecimal value, start.
token = String();
if (step == STEP_START_BEGIN) {
step = STEP_START_READ_HEX;
} else {
step = STEP_END_READ_HEX;
}
c++; // Skip "x".
continue;
} else if (range[c] == '\'' || range[c] == '\"') {
if ((c <= range.length() - 3) && (range[c + 2] == '\'' || range[c + 2] == '\"')) {
token = String();
if (step == STEP_START_BEGIN) {
start = range.unicode_at(c + 1);
if ((c <= range.length() - 4) && (range[c + 3] == ',')) {
list.push_back(start);
step = STEP_START_BEGIN;
} else {
step = STEP_END_BEGIN;
}
} else {
end = range.unicode_at(c + 1);
step = STEP_ADVANCE_BEGIN;
}
c = c + 2; // Skip the rest or token.
continue;
}
} else if (is_digit(range[c])) {
// Read decimal value, start.
token = String();
token += range[c];
if (step == STEP_START_BEGIN) {
step = STEP_START_READ_DEC;
} else {
step = STEP_END_READ_DEC;
}
continue;
}
[[fallthrough]];
}
case STEP_ADVANCE_BEGIN:
case STEP_OFF_X_BEGIN:
case STEP_OFF_Y_BEGIN: {
// Read advance and offset.
if (range[c] == ' ') {
int next = range.find_char(' ', c + 1);
if (next < c) {
next = range.length();
}
if (step == STEP_OFF_X_BEGIN) {
chr_off.x = range.substr(c + 1, next - (c + 1)).to_int();
step = STEP_OFF_Y_BEGIN;
} else if (step == STEP_OFF_Y_BEGIN) {
chr_off.y = range.substr(c + 1, next - (c + 1)).to_int();
step = STEP_FINISHED;
} else {
chr_adv = range.substr(c + 1, next - (c + 1)).to_int();
step = STEP_OFF_X_BEGIN;
}
c = next - 1;
continue;
}
} break;
case STEP_START_READ_HEX:
case STEP_END_READ_HEX: {
// Read hexadecimal value.
if (is_hex_digit(range[c])) {
token += range[c];
} else {
if (step == STEP_START_READ_HEX) {
start = token.hex_to_int();
if (range[c] == ',') {
list.push_back(start);
step = STEP_START_BEGIN;
} else {
step = STEP_END_BEGIN;
}
} else {
end = token.hex_to_int();
step = STEP_ADVANCE_BEGIN;
c--;
}
}
} break;
case STEP_START_READ_DEC:
case STEP_END_READ_DEC: {
// Read decimal value.
if (is_digit(range[c])) {
token += range[c];
} else {
if (step == STEP_START_READ_DEC) {
start = token.to_int();
if (range[c] == ',') {
list.push_back(start);
step = STEP_START_BEGIN;
} else {
step = STEP_END_BEGIN;
}
} else {
end = token.to_int();
step = STEP_ADVANCE_BEGIN;
c--;
}
}
} break;
default: {
WARN_PRINT(vformat("Invalid character \"%d\" in the range: \"%s\"", c, range));
} break;
}
}
if (step == STEP_START_READ_HEX) {
start = token.hex_to_int();
} else if (step == STEP_START_READ_DEC) {
start = token.to_int();
} else if (step == STEP_END_READ_HEX) {
end = token.hex_to_int();
} else if (step == STEP_END_READ_DEC) {
end = token.to_int();
}
if (end == -1) {
end = start;
}
if (!list.is_empty() && end != list[list.size() - 1]) {
list.push_back(end);
}
if (start == -1) {
WARN_PRINT(vformat("Invalid range: \"%s\"", range));
continue;
}
}
if (!list.is_empty()) {
ERR_FAIL_COND_V_MSG(list.size() > remaining, ERR_CANT_CREATE, vformat("Too many characters in range \"%s\", got %d but expected be %d.", range, list.size(), remaining));
for (int32_t idx : list) {
int x = pos % columns;
int y = pos / columns;
font->set_glyph_advance(0, chr_height, idx, Vector2(chr_width + chr_adv, 0));
font->set_glyph_offset(0, Vector2i(chr_height, 0), idx, Vector2i(0, -0.5 * chr_height) + chr_off);
font->set_glyph_size(0, Vector2i(chr_height, 0), idx, Vector2(chr_width, chr_height));
font->set_glyph_uv_rect(0, Vector2i(chr_height, 0), idx, Rect2(img_margin.position.x + chr_cell_width * x + char_margin.position.x, img_margin.position.y + chr_cell_height * y + char_margin.position.y, chr_width, chr_height));
font->set_glyph_texture_idx(0, Vector2i(chr_height, 0), idx, 0);
pos++;
}
remaining -= list.size();
} else {
ERR_FAIL_COND_V_MSG(Math::abs(end - start) > remaining, ERR_CANT_CREATE, vformat("Too many characters in range \"%s\", got %d but expected %d.", range, Math::abs(end - start), remaining));
for (int32_t idx = MIN(start, end); idx <= MAX(start, end); idx++) {
int x = pos % columns;
int y = pos / columns;
font->set_glyph_advance(0, chr_height, idx, Vector2(chr_width + chr_adv, 0));
font->set_glyph_offset(0, Vector2i(chr_height, 0), idx, Vector2i(0, -0.5 * chr_height) + chr_off);
font->set_glyph_size(0, Vector2i(chr_height, 0), idx, Vector2(chr_width, chr_height));
font->set_glyph_uv_rect(0, Vector2i(chr_height, 0), idx, Rect2(img_margin.position.x + chr_cell_width * x + char_margin.position.x, img_margin.position.y + chr_cell_height * y + char_margin.position.y, chr_width, chr_height));
font->set_glyph_texture_idx(0, Vector2i(chr_height, 0), idx, 0);
pos++;
}
remaining -= abs(end - start);
}
}
for (const String &kp : kern) {
const Vector<String> &kp_tokens = kp.split(" ");
if (kp_tokens.size() != 3) {
WARN_PRINT(vformat("Invalid kerning pairs string: \"%s\"", kp));
continue;
}
String from_tokens;
for (int i = 0; i < kp_tokens[0].length(); i++) {
if (i <= kp_tokens[0].length() - 6 && kp_tokens[0][i] == '\\' && kp_tokens[0][i + 1] == 'u' && is_hex_digit(kp_tokens[0][i + 2]) && is_hex_digit(kp_tokens[0][i + 3]) && is_hex_digit(kp_tokens[0][i + 4]) && is_hex_digit(kp_tokens[0][i + 5])) {
char32_t charcode = kp_tokens[0].substr(i + 2, 4).hex_to_int();
from_tokens += charcode;
i += 5;
} else {
from_tokens += kp_tokens[0][i];
}
}
String to_tokens;
for (int i = 0; i < kp_tokens[1].length(); i++) {
if (i <= kp_tokens[1].length() - 6 && kp_tokens[1][i] == '\\' && kp_tokens[1][i + 1] == 'u' && is_hex_digit(kp_tokens[1][i + 2]) && is_hex_digit(kp_tokens[1][i + 3]) && is_hex_digit(kp_tokens[1][i + 4]) && is_hex_digit(kp_tokens[1][i + 5])) {
char32_t charcode = kp_tokens[1].substr(i + 2, 4).hex_to_int();
to_tokens += charcode;
i += 5;
} else {
to_tokens += kp_tokens[1][i];
}
}
int offset = kp_tokens[2].to_int();
for (int a = 0; a < from_tokens.length(); a++) {
for (int b = 0; b < to_tokens.length(); b++) {
font->set_kerning(0, chr_height, Vector2i(from_tokens.unicode_at(a), to_tokens.unicode_at(b)), Vector2(offset, 0));
}
}
}
if (ascent > 0) {
font->set_cache_ascent(0, chr_height, ascent);
} else {
font->set_cache_ascent(0, chr_height, 0.5 * chr_height);
}
if (descent > 0) {
font->set_cache_descent(0, chr_height, descent);
} else {
font->set_cache_descent(0, chr_height, 0.5 * chr_height);
}
int flg = 0;
if ((bool)p_options["compress"]) {
flg |= ResourceSaver::SaverFlags::FLAG_COMPRESS;
}
print_verbose("Saving to: " + p_save_path + ".fontdata");
err = ResourceSaver::save(font, p_save_path + ".fontdata", flg);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save font to file \"" + p_save_path + ".res\".");
print_verbose("Done saving to: " + p_save_path + ".fontdata");
return OK;
}

View File

@@ -0,0 +1,53 @@
/**************************************************************************/
/* resource_importer_imagefont.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. */
/**************************************************************************/
#pragma once
#include "core/io/resource_importer.h"
#include "scene/resources/font.h"
#include "servers/text_server.h"
class ResourceImporterImageFont : public ResourceImporter {
GDCLASS(ResourceImporterImageFont, 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 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(ResourceUID::ID p_source_id, 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 can_import_threaded() const override { return true; }
};

View File

@@ -0,0 +1,593 @@
/**************************************************************************/
/* resource_importer_layered_texture.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_layered_texture.h"
#include "core/config/project_settings.h"
#include "core/error/error_macros.h"
#include "core/io/image_loader.h"
#include "core/object/ref_counted.h"
#include "editor/file_system/editor_file_system.h"
#include "editor/import/resource_importer_texture.h"
#include "editor/import/resource_importer_texture_settings.h"
#include "scene/resources/compressed_texture.h"
String ResourceImporterLayeredTexture::get_importer_name() const {
switch (mode) {
case MODE_CUBEMAP: {
return "cubemap_texture";
} break;
case MODE_2D_ARRAY: {
return "2d_array_texture";
} break;
case MODE_CUBEMAP_ARRAY: {
return "cubemap_array_texture";
} break;
case MODE_3D: {
return "3d_texture";
} break;
}
ERR_FAIL_V("");
}
String ResourceImporterLayeredTexture::get_visible_name() const {
switch (mode) {
case MODE_CUBEMAP: {
return "Cubemap";
} break;
case MODE_2D_ARRAY: {
return "Texture2DArray";
} break;
case MODE_CUBEMAP_ARRAY: {
return "CubemapArray";
} break;
case MODE_3D: {
return "Texture3D";
} break;
}
ERR_FAIL_V("");
}
void ResourceImporterLayeredTexture::get_recognized_extensions(List<String> *p_extensions) const {
ImageLoader::get_recognized_extensions(p_extensions);
}
String ResourceImporterLayeredTexture::get_save_extension() const {
switch (mode) {
case MODE_CUBEMAP: {
return "ccube";
} break;
case MODE_2D_ARRAY: {
return "ctexarray";
} break;
case MODE_CUBEMAP_ARRAY: {
return "ccubearray";
} break;
case MODE_3D: {
return "ctex3d";
} break;
}
ERR_FAIL_V(String());
}
String ResourceImporterLayeredTexture::get_resource_type() const {
switch (mode) {
case MODE_CUBEMAP: {
return "CompressedCubemap";
} break;
case MODE_2D_ARRAY: {
return "CompressedTexture2DArray";
} break;
case MODE_CUBEMAP_ARRAY: {
return "CompressedCubemapArray";
} break;
case MODE_3D: {
return "CompressedTexture3D";
} break;
}
ERR_FAIL_V(String());
}
bool ResourceImporterLayeredTexture::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
if (p_option == "compress/lossy_quality" && p_options.has("compress/mode")) {
return int(p_options["compress/mode"]) == COMPRESS_LOSSY;
}
if ((p_option == "compress/high_quality" || p_option == "compress/hdr_compression") && p_options.has("compress/mode")) {
return int(p_options["compress/mode"]) == COMPRESS_VRAM_COMPRESSED;
}
if (p_option == "compress/uastc_level" || p_option == "compress/rdo_quality_loss") {
return int(p_options["compress/mode"]) == COMPRESS_BASIS_UNIVERSAL;
}
return true;
}
int ResourceImporterLayeredTexture::get_preset_count() const {
return 0;
}
String ResourceImporterLayeredTexture::get_preset_name(int p_idx) const {
return "";
}
void ResourceImporterLayeredTexture::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Lossless,Lossy,VRAM Compressed,VRAM Uncompressed,Basis Universal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress/high_quality"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "compress/lossy_quality", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.7));
Image::BasisUniversalPackerParams basisu_params;
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/uastc_level", PROPERTY_HINT_ENUM, "Fastest,Faster,Medium,Slower,Slowest"), basisu_params.uastc_level));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "compress/rdo_quality_loss", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater"), basisu_params.rdo_quality_loss));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/hdr_compression", PROPERTY_HINT_ENUM, "Disabled,Opaque Only,Always"), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/channel_pack", PROPERTY_HINT_ENUM, "sRGB Friendly,Optimized,Normal Map (RG Channels)"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "mipmaps/generate"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "mipmaps/limit", PROPERTY_HINT_RANGE, "-1,256"), -1));
if (mode == MODE_2D_ARRAY || mode == MODE_3D) {
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slices/horizontal", PROPERTY_HINT_RANGE, "1,256,1"), 8));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slices/vertical", PROPERTY_HINT_RANGE, "1,256,1"), 8));
}
if (mode == MODE_CUBEMAP || mode == MODE_CUBEMAP_ARRAY) {
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slices/arrangement", PROPERTY_HINT_ENUM, "1x6,2x3,3x2,6x1"), 1));
if (mode == MODE_CUBEMAP_ARRAY) {
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slices/layout", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slices/amount", PROPERTY_HINT_RANGE, "1,1024,1,or_greater"), 1));
}
}
}
void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, const String &p_to_path, int p_compress_mode, float p_lossy, const Image::BasisUniversalPackerParams &p_basisu_params, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2) {
Vector<Ref<Image>> mipmap_images; //for 3D
if (mode == MODE_3D) {
//3D saves in its own way
for (int i = 0; i < p_images.size(); i++) {
if (p_images.write[i]->has_mipmaps()) {
p_images.write[i]->clear_mipmaps();
}
if (p_force_po2) {
p_images.write[i]->resize_to_po2();
}
}
if (p_mipmaps) {
Vector<Ref<Image>> parent_images = p_images;
//create 3D mipmaps, this is horrible, though not used very often
int w = p_images[0]->get_width();
int h = p_images[0]->get_height();
int d = p_images.size();
while (w > 1 || h > 1 || d > 1) {
Vector<Ref<Image>> mipmaps;
int mm_w = MAX(1, w >> 1);
int mm_h = MAX(1, h >> 1);
int mm_d = MAX(1, d >> 1);
for (int i = 0; i < mm_d; i++) {
Ref<Image> mm = Image::create_empty(mm_w, mm_h, false, p_images[0]->get_format());
Vector3 pos;
pos.z = float(i) * float(d) / float(mm_d) + 0.5;
for (int x = 0; x < mm_w; x++) {
for (int y = 0; y < mm_h; y++) {
pos.x = float(x) * float(w) / float(mm_w) + 0.5;
pos.y = float(y) * float(h) / float(mm_h) + 0.5;
Vector3i posi = Vector3i(pos);
Vector3 fract = pos - Vector3(posi);
Vector3i posi_n = posi;
if (posi_n.x < w - 1) {
posi_n.x++;
}
if (posi_n.y < h - 1) {
posi_n.y++;
}
if (posi_n.z < d - 1) {
posi_n.z++;
}
Color c000 = parent_images[posi.z]->get_pixel(posi.x, posi.y);
Color c100 = parent_images[posi.z]->get_pixel(posi_n.x, posi.y);
Color c010 = parent_images[posi.z]->get_pixel(posi.x, posi_n.y);
Color c110 = parent_images[posi.z]->get_pixel(posi_n.x, posi_n.y);
Color c001 = parent_images[posi_n.z]->get_pixel(posi.x, posi.y);
Color c101 = parent_images[posi_n.z]->get_pixel(posi_n.x, posi.y);
Color c011 = parent_images[posi_n.z]->get_pixel(posi.x, posi_n.y);
Color c111 = parent_images[posi_n.z]->get_pixel(posi_n.x, posi_n.y);
Color cx00 = c000.lerp(c100, fract.x);
Color cx01 = c001.lerp(c101, fract.x);
Color cx10 = c010.lerp(c110, fract.x);
Color cx11 = c011.lerp(c111, fract.x);
Color cy0 = cx00.lerp(cx10, fract.y);
Color cy1 = cx01.lerp(cx11, fract.y);
Color cz = cy0.lerp(cy1, fract.z);
mm->set_pixel(x, y, cz);
}
}
mipmaps.push_back(mm);
}
w = mm_w;
h = mm_h;
d = mm_d;
mipmap_images.append_array(mipmaps);
parent_images = mipmaps;
}
}
} else {
for (int i = 0; i < p_images.size(); i++) {
if (p_force_po2) {
p_images.write[i]->resize_to_po2();
}
if (p_mipmaps) {
p_images.write[i]->generate_mipmaps(p_csource == Image::COMPRESS_SOURCE_NORMAL);
} else {
p_images.write[i]->clear_mipmaps();
}
}
}
Ref<FileAccess> f = FileAccess::open(p_to_path, FileAccess::WRITE);
f->store_8('G');
f->store_8('S');
f->store_8('T');
f->store_8('L');
f->store_32(CompressedTextureLayered::FORMAT_VERSION);
f->store_32(p_images.size()); // For 2d layers or 3d depth.
f->store_32(mode);
f->store_32(0);
f->store_32(0);
f->store_32(mipmap_images.size()); // Adjust the amount of mipmaps.
f->store_32(0);
f->store_32(0);
if ((p_compress_mode == COMPRESS_LOSSLESS || p_compress_mode == COMPRESS_LOSSY) && p_images[0]->get_format() >= Image::FORMAT_RF) {
p_compress_mode = COMPRESS_VRAM_UNCOMPRESSED; // These can't go as lossy.
}
for (int i = 0; i < p_images.size(); i++) {
ResourceImporterTexture::save_to_ctex_format(f, p_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy, p_basisu_params);
}
for (int i = 0; i < mipmap_images.size(); i++) {
ResourceImporterTexture::save_to_ctex_format(f, mipmap_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy, p_basisu_params);
}
}
Error ResourceImporterLayeredTexture::import(ResourceUID::ID p_source_id, 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) {
int compress_mode = p_options["compress/mode"];
float lossy = p_options["compress/lossy_quality"];
bool high_quality = p_options["compress/high_quality"];
int hdr_compression = p_options["compress/hdr_compression"];
bool mipmaps = p_options["mipmaps/generate"];
int channel_pack = p_options["compress/channel_pack"];
int hslices = (p_options.has("slices/horizontal")) ? int(p_options["slices/horizontal"]) : 0;
int vslices = (p_options.has("slices/vertical")) ? int(p_options["slices/vertical"]) : 0;
int arrangement = (p_options.has("slices/arrangement")) ? int(p_options["slices/arrangement"]) : 0;
int layout = (p_options.has("slices/layout")) ? int(p_options["slices/layout"]) : 0;
int amount = (p_options.has("slices/amount")) ? int(p_options["slices/amount"]) : 0;
if (mode == MODE_CUBEMAP || mode == MODE_CUBEMAP_ARRAY) {
switch (arrangement) {
case CUBEMAP_FORMAT_1X6: {
hslices = 1;
vslices = 6;
} break;
case CUBEMAP_FORMAT_2X3: {
hslices = 2;
vslices = 3;
} break;
case CUBEMAP_FORMAT_3X2: {
hslices = 3;
vslices = 2;
} break;
case CUBEMAP_FORMAT_6X1: {
hslices = 6;
vslices = 1;
} break;
}
if (mode == MODE_CUBEMAP_ARRAY) {
if (layout == 0) {
hslices *= amount;
} else {
vslices *= amount;
}
}
}
Ref<Image> image;
image.instantiate();
Error err = ImageLoader::load_image(p_source_file, image);
if (err != OK) {
return err;
}
if (compress_mode == COMPRESS_VRAM_COMPRESSED) {
//if using video ram, optimize
if (channel_pack == 0) {
//remove alpha if not needed, so compression is more efficient
if (image->get_format() == Image::FORMAT_RGBA8 && image->detect_alpha() == Image::ALPHA_NONE) {
image->convert(Image::FORMAT_RGB8);
}
} else if (image->get_format() < Image::FORMAT_RGBA8) {
image->optimize_channels();
}
}
Image::CompressSource csource = Image::COMPRESS_SOURCE_GENERIC;
if (channel_pack == 0) {
csource = Image::COMPRESS_SOURCE_SRGB;
} else if (channel_pack == 2) {
// force normal
csource = Image::COMPRESS_SOURCE_NORMAL;
}
Image::UsedChannels used_channels = image->detect_used_channels(csource);
Vector<Ref<Image>> slices;
int slice_w = image->get_width() / hslices;
int slice_h = image->get_height() / vslices;
for (int i = 0; i < vslices; i++) {
for (int j = 0; j < hslices; j++) {
int x = slice_w * j;
int y = slice_h * i;
Ref<Image> slice = image->get_region(Rect2i(x, y, slice_w, slice_h));
ERR_CONTINUE(slice.is_null() || slice->is_empty());
if (slice->get_width() != slice_w || slice->get_height() != slice_h) {
slice->resize(slice_w, slice_h);
}
slices.push_back(slice);
}
}
const Image::BasisUniversalPackerParams basisu_params = {
p_options["compress/uastc_level"],
p_options["compress/rdo_quality_loss"],
};
Array formats_imported;
Ref<LayeredTextureImport> texture_import;
texture_import.instantiate();
texture_import->csource = &csource;
texture_import->save_path = p_save_path;
texture_import->options = p_options;
texture_import->platform_variants = r_platform_variants;
texture_import->image = image;
texture_import->formats_imported = formats_imported;
texture_import->slices = &slices;
texture_import->compress_mode = compress_mode;
texture_import->lossy = lossy;
texture_import->hdr_compression = hdr_compression;
texture_import->mipmaps = mipmaps;
texture_import->used_channels = used_channels;
texture_import->high_quality = high_quality;
texture_import->basisu_params = basisu_params;
_check_compress_ctex(p_source_file, texture_import);
if (r_metadata) {
Dictionary meta;
meta["vram_texture"] = compress_mode == COMPRESS_VRAM_COMPRESSED;
if (formats_imported.size()) {
meta["imported_formats"] = formats_imported;
}
*r_metadata = meta;
}
return OK;
}
const char *ResourceImporterLayeredTexture::compression_formats[] = {
"s3tc_bptc",
"etc2_astc",
nullptr
};
String ResourceImporterLayeredTexture::get_import_settings_string() const {
String s;
int index = 0;
while (compression_formats[index]) {
String setting_path = "rendering/textures/vram_compression/import_" + String(compression_formats[index]);
bool test = GLOBAL_GET(setting_path);
if (test) {
s += String(compression_formats[index]);
}
index++;
}
return s;
}
bool ResourceImporterLayeredTexture::are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const {
//will become invalid if formats are missing to import
if (!p_meta.has("vram_texture")) {
return false;
}
bool vram = p_meta["vram_texture"];
if (!vram) {
return true; //do not care about non vram
}
Vector<String> formats_imported;
if (p_meta.has("imported_formats")) {
formats_imported = p_meta["imported_formats"];
}
int index = 0;
bool valid = true;
while (compression_formats[index]) {
String setting_path = "rendering/textures/vram_compression/import_" + String(compression_formats[index]);
if (ProjectSettings::get_singleton()->has_setting(setting_path)) {
bool test = GLOBAL_GET(setting_path);
if (test) {
if (!formats_imported.has(compression_formats[index])) {
valid = false;
break;
}
}
} else {
WARN_PRINT("Setting for imported format not found: " + setting_path);
}
index++;
}
return valid;
}
ResourceImporterLayeredTexture *ResourceImporterLayeredTexture::singleton = nullptr;
ResourceImporterLayeredTexture::ResourceImporterLayeredTexture(bool p_singleton) {
// This should only be set through the EditorNode.
if (p_singleton) {
singleton = this;
}
mode = MODE_CUBEMAP;
}
ResourceImporterLayeredTexture::~ResourceImporterLayeredTexture() {
if (singleton == this) {
singleton = nullptr;
}
}
void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source_file, Ref<LayeredTextureImport> r_texture_import) {
String extension = get_save_extension();
ERR_FAIL_NULL(r_texture_import->csource);
if (r_texture_import->compress_mode != COMPRESS_VRAM_COMPRESSED) {
// Import normally.
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + extension, r_texture_import->compress_mode, r_texture_import->lossy, r_texture_import->basisu_params,
Image::COMPRESS_S3TC /* IGNORED */, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, false);
return;
}
// Must import in all formats, in order of priority (so platform chooses the best supported one. IE, etc2 over etc).
// Android, GLES 2.x
const bool can_s3tc_bptc = ResourceImporterTextureSettings::should_import_s3tc_bptc();
const bool can_etc2_astc = ResourceImporterTextureSettings::should_import_etc2_astc();
// Add list of formats imported
if (can_s3tc_bptc) {
r_texture_import->formats_imported.push_back("s3tc_bptc");
}
if (can_etc2_astc) {
r_texture_import->formats_imported.push_back("etc2_astc");
}
bool can_compress_hdr = r_texture_import->hdr_compression > 0;
ERR_FAIL_COND(r_texture_import->image.is_null());
bool is_hdr = (r_texture_import->image->get_format() >= Image::FORMAT_RF && r_texture_import->image->get_format() <= Image::FORMAT_RGBE9995);
ERR_FAIL_NULL(r_texture_import->slices);
// Can compress hdr, but hdr with alpha is not compressible.
bool use_uncompressed = false;
if (is_hdr) {
if (r_texture_import->used_channels == Image::USED_CHANNELS_LA || r_texture_import->used_channels == Image::USED_CHANNELS_RGBA) {
if (r_texture_import->hdr_compression == 2) {
// The user selected to compress hdr anyway, so force an alpha-less format.
if (r_texture_import->image->get_format() == Image::FORMAT_RGBAF) {
for (int i = 0; i < r_texture_import->slices->size(); i++) {
r_texture_import->slices->write[i]->convert(Image::FORMAT_RGBF);
}
} else if (r_texture_import->image->get_format() == Image::FORMAT_RGBAH) {
for (int i = 0; i < r_texture_import->slices->size(); i++) {
r_texture_import->slices->write[i]->convert(Image::FORMAT_RGBH);
}
}
} else {
can_compress_hdr = false;
}
}
if (!can_compress_hdr) {
//default to rgbe
if (r_texture_import->image->get_format() != Image::FORMAT_RGBE9995) {
for (int i = 0; i < r_texture_import->slices->size(); i++) {
r_texture_import->slices->write[i]->convert(Image::FORMAT_RGBE9995);
}
}
use_uncompressed = true;
}
}
if (use_uncompressed) {
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + extension, COMPRESS_VRAM_UNCOMPRESSED, r_texture_import->lossy, r_texture_import->basisu_params,
Image::COMPRESS_S3TC /* IGNORED */, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, false);
} else {
if (can_s3tc_bptc) {
Image::CompressMode image_compress_mode;
String image_compress_format;
if (r_texture_import->high_quality || is_hdr) {
image_compress_mode = Image::COMPRESS_BPTC;
image_compress_format = "bptc";
} else {
image_compress_mode = Image::COMPRESS_S3TC;
image_compress_format = "s3tc";
}
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + image_compress_format + "." + extension, r_texture_import->compress_mode, r_texture_import->lossy, r_texture_import->basisu_params, image_compress_mode, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, true);
r_texture_import->platform_variants->push_back(image_compress_format);
}
if (can_etc2_astc) {
Image::CompressMode image_compress_mode;
String image_compress_format;
if (r_texture_import->high_quality || is_hdr) {
image_compress_mode = Image::COMPRESS_ASTC;
image_compress_format = "astc";
} else {
image_compress_mode = Image::COMPRESS_ETC2;
image_compress_format = "etc2";
}
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + image_compress_format + "." + extension, r_texture_import->compress_mode, r_texture_import->lossy, r_texture_import->basisu_params, image_compress_mode, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, true);
r_texture_import->platform_variants->push_back(image_compress_format);
}
}
}

View File

@@ -0,0 +1,127 @@
/**************************************************************************/
/* resource_importer_layered_texture.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. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
#include "core/io/resource_importer.h"
#include "core/object/ref_counted.h"
class CompressedTexture2D;
class LayeredTextureImport : public RefCounted {
GDCLASS(LayeredTextureImport, RefCounted);
public:
Image::CompressSource *csource = nullptr;
String save_path;
HashMap<StringName, Variant> options;
List<String> *platform_variants = nullptr;
Ref<Image> image = nullptr;
Array formats_imported;
Vector<Ref<Image>> *slices = nullptr;
int compress_mode = 0;
float lossy = 1.0;
Image::BasisUniversalPackerParams basisu_params;
int hdr_compression = 0;
bool mipmaps = true;
bool high_quality = false;
Image::UsedChannels used_channels = Image::USED_CHANNELS_RGBA;
};
class ResourceImporterLayeredTexture : public ResourceImporter {
GDCLASS(ResourceImporterLayeredTexture, ResourceImporter);
public:
enum Mode {
MODE_2D_ARRAY,
MODE_CUBEMAP,
MODE_CUBEMAP_ARRAY,
MODE_3D,
};
enum CubemapFormat {
CUBEMAP_FORMAT_1X6,
CUBEMAP_FORMAT_2X3,
CUBEMAP_FORMAT_3X2,
CUBEMAP_FORMAT_6X1,
};
enum TextureFlags {
TEXTURE_FLAGS_MIPMAPS = 1
};
private:
Mode mode;
static const char *compression_formats[];
protected:
static ResourceImporterLayeredTexture *singleton;
public:
void _check_compress_ctex(const String &p_source_file, Ref<LayeredTextureImport> r_texture_import);
static ResourceImporterLayeredTexture *get_singleton() { return singleton; }
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;
enum CompressMode {
COMPRESS_LOSSLESS,
COMPRESS_LOSSY,
COMPRESS_VRAM_COMPRESSED,
COMPRESS_VRAM_UNCOMPRESSED,
COMPRESS_BASIS_UNIVERSAL
};
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;
void _save_tex(Vector<Ref<Image>> p_images, const String &p_to_path, int p_compress_mode, float p_lossy, const Image::BasisUniversalPackerParams &p_basisu_params, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2);
virtual Error import(ResourceUID::ID p_source_id, 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 are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const override;
virtual String get_import_settings_string() const override;
virtual bool can_import_threaded() const override { return true; }
void set_mode(Mode p_mode) { mode = p_mode; }
ResourceImporterLayeredTexture(bool p_singleton = false);
~ResourceImporterLayeredTexture();
};

View File

@@ -0,0 +1,112 @@
/**************************************************************************/
/* resource_importer_shader_file.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_shader_file.h"
#include "core/io/file_access.h"
#include "core/io/resource_saver.h"
#include "editor/editor_node.h"
#include "editor/shader/shader_file_editor_plugin.h"
#include "servers/rendering/rendering_device_binds.h"
String ResourceImporterShaderFile::get_importer_name() const {
return "glsl";
}
String ResourceImporterShaderFile::get_visible_name() const {
return "GLSL Shader File";
}
void ResourceImporterShaderFile::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("glsl");
}
String ResourceImporterShaderFile::get_save_extension() const {
return "res";
}
String ResourceImporterShaderFile::get_resource_type() const {
return "RDShaderFile";
}
int ResourceImporterShaderFile::get_preset_count() const {
return 0;
}
String ResourceImporterShaderFile::get_preset_name(int p_idx) const {
return String();
}
void ResourceImporterShaderFile::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
}
bool ResourceImporterShaderFile::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
return true;
}
static String _include_function(const String &p_path, void *userpointer) {
Error err;
String *base_path = (String *)userpointer;
String include = p_path;
if (include.is_relative_path()) {
include = base_path->path_join(include);
}
Ref<FileAccess> file_inc = FileAccess::open(include, FileAccess::READ, &err);
if (err != OK) {
return String();
}
return file_inc->get_as_utf8_string();
}
Error ResourceImporterShaderFile::import(ResourceUID::ID p_source_id, 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) {
Error err;
Ref<FileAccess> file = FileAccess::open(p_source_file, FileAccess::READ, &err);
ERR_FAIL_COND_V(err != OK, ERR_CANT_OPEN);
ERR_FAIL_COND_V(file.is_null(), ERR_CANT_OPEN);
String file_txt = file->get_as_utf8_string();
Ref<RDShaderFile> shader_file;
shader_file.instantiate();
String base_path = p_source_file.get_base_dir();
err = shader_file->parse_versions_from_text(file_txt, "", _include_function, &base_path);
if (err != OK) {
if (!ShaderFileEditor::singleton->is_visible_in_tree()) {
callable_mp_static(&EditorNode::add_io_error).call_deferred(vformat(TTR("Error importing GLSL shader file: '%s'. Open the file in the filesystem dock in order to see the reason."), p_source_file));
}
}
ResourceSaver::save(shader_file, p_save_path + ".res");
return OK;
}

View File

@@ -0,0 +1,54 @@
/**************************************************************************/
/* resource_importer_shader_file.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. */
/**************************************************************************/
#pragma once
#include "core/io/resource_importer.h"
class ResourceImporterShaderFile : public ResourceImporter {
GDCLASS(ResourceImporterShaderFile, 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_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(ResourceUID::ID p_source_id, 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 can_import_threaded() const override { return true; }
};

View File

@@ -0,0 +1,104 @@
/**************************************************************************/
/* resource_importer_svg.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_svg.h"
#include "core/io/file_access.h"
#include "scene/resources/dpi_texture.h"
String ResourceImporterSVG::get_importer_name() const {
return "svg";
}
String ResourceImporterSVG::get_visible_name() const {
return "DPITexture";
}
void ResourceImporterSVG::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("svg");
}
String ResourceImporterSVG::get_save_extension() const {
return "dpitex";
}
String ResourceImporterSVG::get_resource_type() const {
return "DPITexture";
}
bool ResourceImporterSVG::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
return true;
}
int ResourceImporterSVG::get_preset_count() const {
return 0;
}
String ResourceImporterSVG::get_preset_name(int p_idx) const {
return String();
}
void ResourceImporterSVG::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "base_scale", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 1.0));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "saturation", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), 1.0));
r_options->push_back(ImportOption(PropertyInfo(Variant::DICTIONARY, "color_map"), Dictionary()));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
}
Error ResourceImporterSVG::import(ResourceUID::ID p_source_id, 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) {
Ref<DPITexture> dpi_tex;
dpi_tex.instantiate();
String source = FileAccess::get_file_as_string(p_source_file);
ERR_FAIL_COND_V_MSG(source.is_empty(), ERR_CANT_OPEN, vformat("Cannot open file from path \"%s\".", p_source_file));
double base_scale = p_options["base_scale"];
double saturation = p_options["saturation"];
Dictionary color_map = p_options["color_map"];
dpi_tex->set_base_scale(base_scale);
dpi_tex->set_saturation(saturation);
dpi_tex->set_color_map(color_map);
dpi_tex->set_source(source);
ERR_FAIL_COND_V_MSG(dpi_tex->get_rid().is_null(), ERR_CANT_OPEN, vformat("Failed loading SVG, unsupported or invalid SVG data in \"%s\".", p_source_file));
int flg = 0;
if ((bool)p_options["compress"]) {
flg |= ResourceSaver::SaverFlags::FLAG_COMPRESS;
}
print_verbose("Saving to: " + p_save_path + ".dpitex");
Error err = ResourceSaver::save(dpi_tex, p_save_path + ".dpitex", flg);
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot save DPI texture to file \"%s.dpitex\".", p_save_path));
print_verbose("Done saving to: " + p_save_path + ".dpitex");
return OK;
}

View File

@@ -0,0 +1,54 @@
/**************************************************************************/
/* resource_importer_svg.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. */
/**************************************************************************/
#pragma once
#include "core/io/resource_importer.h"
class ResourceImporterSVG : public ResourceImporter {
GDCLASS(ResourceImporterSVG, 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_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(ResourceUID::ID p_source_id, 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 can_import_threaded() const override { return true; }
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,130 @@
/**************************************************************************/
/* resource_importer_texture.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. */
/**************************************************************************/
#pragma once
#include "core/io/file_access.h"
#include "core/io/image.h"
#include "core/io/resource_importer.h"
#include "servers/rendering_server.h"
class CompressedTexture2D;
class ResourceImporterTexture : public ResourceImporter {
GDCLASS(ResourceImporterTexture, ResourceImporter);
public:
enum CompressMode {
COMPRESS_LOSSLESS,
COMPRESS_LOSSY,
COMPRESS_VRAM_COMPRESSED,
COMPRESS_VRAM_UNCOMPRESSED,
COMPRESS_BASIS_UNIVERSAL
};
enum ChannelRemap {
REMAP_R,
REMAP_G,
REMAP_B,
REMAP_A,
REMAP_INV_R,
REMAP_INV_G,
REMAP_INV_B,
REMAP_INV_A,
REMAP_UNUSED,
REMAP_0,
REMAP_1,
};
protected:
enum {
MAKE_3D_FLAG = 1,
MAKE_ROUGHNESS_FLAG = 2,
MAKE_NORMAL_FLAG = 4
};
Mutex mutex;
struct MakeInfo {
int flags = 0;
String normal_path_for_roughness;
RS::TextureDetectRoughnessChannel channel_for_roughness = RS::TEXTURE_DETECT_ROUGHNESS_R;
};
HashMap<StringName, MakeInfo> make_flags;
static void _texture_reimport_roughness(const Ref<CompressedTexture2D> &p_tex, const String &p_normal_path, RenderingServer::TextureDetectRoughnessChannel p_channel);
static void _texture_reimport_3d(const Ref<CompressedTexture2D> &p_tex);
static void _texture_reimport_normal(const Ref<CompressedTexture2D> &p_tex);
static ResourceImporterTexture *singleton;
static const char *compression_formats[];
void _save_ctex(const Ref<Image> &p_image, const String &p_to_path, CompressMode p_compress_mode, float p_lossy_quality, const Image::BasisUniversalPackerParams &p_basisu_params, Image::CompressMode p_vram_compression, bool p_mipmaps, bool p_streamable, bool p_detect_3d, bool p_detect_srgb, bool p_detect_normal, bool p_force_normal, bool p_srgb_friendly, bool p_force_po2_for_compressed, uint32_t p_limit_mipmap, const Ref<Image> &p_normal, Image::RoughnessChannel p_roughness_channel);
void _save_editor_meta(const Dictionary &p_metadata, const String &p_to_path);
Dictionary _load_editor_meta(const String &p_to_path) const;
static inline void _remap_channels(Ref<Image> &r_image, ChannelRemap p_options[4]);
static inline void _clamp_hdr_exposure(Ref<Image> &r_image);
static inline void _invert_y_channel(Ref<Image> &r_image);
public:
static void save_to_ctex_format(Ref<FileAccess> f, const Ref<Image> &p_image, CompressMode p_compress_mode, Image::UsedChannels p_channels, Image::CompressMode p_compress_format, float p_lossy_quality, const Image::BasisUniversalPackerParams &p_basisu_params);
static ResourceImporterTexture *get_singleton() { return singleton; }
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;
enum Preset {
PRESET_DETECT,
PRESET_2D,
PRESET_3D,
};
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(ResourceUID::ID p_source_id, 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 can_import_threaded() const override { return true; }
void update_imports();
virtual bool are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const override;
virtual String get_import_settings_string() const override;
ResourceImporterTexture(bool p_singleton = false);
~ResourceImporterTexture();
};

View File

@@ -0,0 +1,406 @@
/**************************************************************************/
/* resource_importer_texture_atlas.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_texture_atlas.h"
#include "atlas_import_failed.xpm"
#include "core/config/project_settings.h"
#include "core/io/image_loader.h"
#include "core/io/resource_saver.h"
#include "core/math/geometry_2d.h"
#include "editor/import/editor_atlas_packer.h"
#include "scene/resources/atlas_texture.h"
#include "scene/resources/bit_map.h"
#include "scene/resources/image_texture.h"
#include "scene/resources/mesh.h"
#include "scene/resources/mesh_texture.h"
String ResourceImporterTextureAtlas::get_importer_name() const {
return "texture_atlas";
}
String ResourceImporterTextureAtlas::get_visible_name() const {
return "TextureAtlas";
}
void ResourceImporterTextureAtlas::get_recognized_extensions(List<String> *p_extensions) const {
ImageLoader::get_recognized_extensions(p_extensions);
}
String ResourceImporterTextureAtlas::get_save_extension() const {
return "res";
}
String ResourceImporterTextureAtlas::get_resource_type() const {
return "Texture2D";
}
bool ResourceImporterTextureAtlas::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
if (p_option == "crop_to_region" && int(p_options["import_mode"]) != IMPORT_MODE_REGION) {
return false;
} else if (p_option == "trim_alpha_border_from_region" && int(p_options["import_mode"]) != IMPORT_MODE_REGION) {
return false;
}
return true;
}
int ResourceImporterTextureAtlas::get_preset_count() const {
return 0;
}
String ResourceImporterTextureAtlas::get_preset_name(int p_idx) const {
return String();
}
void ResourceImporterTextureAtlas::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "atlas_file", PROPERTY_HINT_SAVE_FILE, "*.png"), ""));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_mode", PROPERTY_HINT_ENUM, "Region,Mesh2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "crop_to_region"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "trim_alpha_border_from_region"), true));
}
String ResourceImporterTextureAtlas::get_option_group_file() const {
return "atlas_file";
}
Error ResourceImporterTextureAtlas::import(ResourceUID::ID p_source_id, 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) {
/* If this happens, it's because the atlas_file field was not filled, so just import a broken texture */
//use an xpm because it's size independent, the editor images are vector and size dependent
//it's a simple hack
Ref<Image> broken = memnew(Image((const char **)atlas_import_failed_xpm));
ResourceSaver::save(ImageTexture::create_from_image(broken), p_save_path + ".tex");
return OK;
}
// FIXME: Rasterization has issues, see https://github.com/godotengine/godot/issues/68350#issuecomment-1305610290
static void _plot_triangle(Vector2i *p_vertices, const Vector2i &p_offset, bool p_transposed, Ref<Image> p_image, const Ref<Image> &p_src_image) {
int width = p_image->get_width();
int height = p_image->get_height();
int src_width = p_src_image->get_width();
int src_height = p_src_image->get_height();
int x[3];
int y[3];
for (int j = 0; j < 3; j++) {
x[j] = p_vertices[j].x;
y[j] = p_vertices[j].y;
}
// sort the points vertically
if (y[1] > y[2]) {
SWAP(x[1], x[2]);
SWAP(y[1], y[2]);
}
if (y[0] > y[1]) {
SWAP(x[0], x[1]);
SWAP(y[0], y[1]);
}
if (y[1] > y[2]) {
SWAP(x[1], x[2]);
SWAP(y[1], y[2]);
}
double dx_far = double(x[2] - x[0]) / (y[2] - y[0] + 1);
double dx_upper = double(x[1] - x[0]) / (y[1] - y[0] + 1);
double dx_low = double(x[2] - x[1]) / (y[2] - y[1] + 1);
double xf = x[0];
double xt = x[0] + dx_upper; // if y[0] == y[1], special case
int max_y = MIN(y[2], p_transposed ? (width - p_offset.x - 1) : (height - p_offset.y - 1));
for (int yi = y[0]; yi < max_y; yi++) {
if (yi >= 0) {
for (int xi = (xf > 0 ? int(xf) : 0); xi < (xt <= src_width ? xt : src_width); xi++) {
int px = xi, py = yi;
int sx = px, sy = py;
sx = CLAMP(sx, 0, src_width - 1);
sy = CLAMP(sy, 0, src_height - 1);
Color color = p_src_image->get_pixel(sx, sy);
if (p_transposed) {
SWAP(px, py);
}
px += p_offset.x;
py += p_offset.y;
//may have been cropped, so don't blit what is not visible?
if (px < 0 || px >= width) {
continue;
}
if (py < 0 || py >= height) {
continue;
}
p_image->set_pixel(px, py, color);
}
for (int xi = (xf < src_width ? int(xf) : src_width - 1); xi >= (xt > 0 ? xt : 0); xi--) {
int px = xi, py = yi;
int sx = px, sy = py;
sx = CLAMP(sx, 0, src_width - 1);
sy = CLAMP(sy, 0, src_height - 1);
Color color = p_src_image->get_pixel(sx, sy);
if (p_transposed) {
SWAP(px, py);
}
px += p_offset.x;
py += p_offset.y;
//may have been cropped, so don't blit what is not visible?
if (px < 0 || px >= width) {
continue;
}
if (py < 0 || py >= height) {
continue;
}
p_image->set_pixel(px, py, color);
}
}
xf += dx_far;
if (yi < y[1]) {
xt += dx_upper;
} else {
xt += dx_low;
}
}
}
Error ResourceImporterTextureAtlas::import_group_file(const String &p_group_file, const HashMap<String, HashMap<StringName, Variant>> &p_source_file_options, const HashMap<String, String> &p_base_paths) {
ERR_FAIL_COND_V(p_source_file_options.is_empty(), ERR_BUG); //should never happen
Vector<EditorAtlasPacker::Chart> charts;
Vector<PackData> pack_data_files;
pack_data_files.resize(p_source_file_options.size());
int idx = 0;
for (const KeyValue<String, HashMap<StringName, Variant>> &E : p_source_file_options) {
PackData &pack_data = pack_data_files.write[idx];
const String &source = E.key;
const HashMap<StringName, Variant> &options = E.value;
Ref<Image> image;
image.instantiate();
Error err = ImageLoader::load_image(source, image);
ERR_CONTINUE(err != OK);
pack_data.image = image;
int mode = options["import_mode"];
if (mode == IMPORT_MODE_REGION) {
pack_data.is_mesh = false;
pack_data.is_cropped = options["crop_to_region"];
EditorAtlasPacker::Chart chart;
Rect2i used_rect = Rect2i(Vector2i(), image->get_size());
if (options["trim_alpha_border_from_region"]) {
// Clip a region from the image.
used_rect = image->get_used_rect();
}
pack_data.region = used_rect;
chart.vertices.push_back(used_rect.position);
chart.vertices.push_back(used_rect.position + Vector2i(used_rect.size.x, 0));
chart.vertices.push_back(used_rect.position + Vector2i(used_rect.size.x, used_rect.size.y));
chart.vertices.push_back(used_rect.position + Vector2i(0, used_rect.size.y));
EditorAtlasPacker::Chart::Face f;
f.vertex[0] = 0;
f.vertex[1] = 1;
f.vertex[2] = 2;
chart.faces.push_back(f);
f.vertex[0] = 0;
f.vertex[1] = 2;
f.vertex[2] = 3;
chart.faces.push_back(f);
chart.can_transpose = false;
pack_data.chart_vertices.push_back(chart.vertices);
pack_data.chart_pieces.push_back(charts.size());
charts.push_back(chart);
} else {
pack_data.is_mesh = true;
Ref<BitMap> bit_map;
bit_map.instantiate();
bit_map->create_from_image_alpha(image);
Vector<Vector<Vector2>> polygons = bit_map->clip_opaque_to_polygons(Rect2(Vector2(), image->get_size()));
for (int j = 0; j < polygons.size(); j++) {
EditorAtlasPacker::Chart chart;
chart.vertices = polygons[j];
chart.can_transpose = true;
Vector<int> poly = Geometry2D::triangulate_polygon(polygons[j]);
for (int i = 0; i < poly.size(); i += 3) {
EditorAtlasPacker::Chart::Face f;
f.vertex[0] = poly[i + 0];
f.vertex[1] = poly[i + 1];
f.vertex[2] = poly[i + 2];
chart.faces.push_back(f);
}
pack_data.chart_pieces.push_back(charts.size());
charts.push_back(chart);
pack_data.chart_vertices.push_back(polygons[j]);
}
}
idx++;
}
const int max_width = (int)GLOBAL_GET("editor/import/atlas_max_width");
//pack the charts
int atlas_width, atlas_height;
EditorAtlasPacker::chart_pack(charts, atlas_width, atlas_height, max_width);
if (atlas_height > max_width * 2) {
WARN_PRINT(vformat(TTR("%s: Atlas texture significantly larger on one axis (%d), consider changing the `editor/import/atlas_max_width` Project Setting to allow a wider texture, making the result more even in size."), p_group_file, atlas_height));
}
//blit the atlas
Ref<Image> new_atlas = Image::create_empty(atlas_width, atlas_height, false, Image::FORMAT_RGBA8);
for (int i = 0; i < pack_data_files.size(); i++) {
PackData &pack_data = pack_data_files.write[i];
for (int j = 0; j < pack_data.chart_pieces.size(); j++) {
const EditorAtlasPacker::Chart &chart = charts[pack_data.chart_pieces[j]];
for (int k = 0; k < chart.faces.size(); k++) {
Vector2i positions[3];
for (int l = 0; l < 3; l++) {
int vertex_idx = chart.faces[k].vertex[l];
positions[l] = Vector2i(chart.vertices[vertex_idx]);
}
_plot_triangle(positions, Vector2i(chart.final_offset), chart.transposed, new_atlas, pack_data.image);
}
}
}
//save the atlas
new_atlas->save_png(p_group_file);
//update cache if existing, else create
Ref<Texture2D> cache;
cache = ResourceCache::get_ref(p_group_file);
if (cache.is_null()) {
Ref<ImageTexture> res_cache = ImageTexture::create_from_image(new_atlas);
res_cache->set_path(p_group_file);
cache = res_cache;
}
//save the images
idx = 0;
for (const KeyValue<String, HashMap<StringName, Variant>> &E : p_source_file_options) {
PackData &pack_data = pack_data_files.write[idx];
Ref<Texture2D> texture;
if (!pack_data.is_mesh) {
Vector2 offset = charts[pack_data.chart_pieces[0]].vertices[0] + charts[pack_data.chart_pieces[0]].final_offset;
//region
Ref<AtlasTexture> atlas_texture;
atlas_texture.instantiate();
atlas_texture->set_atlas(cache);
atlas_texture->set_region(Rect2(offset, pack_data.region.size));
if (!pack_data.is_cropped) {
atlas_texture->set_margin(Rect2(pack_data.region.position, pack_data.image->get_size() - pack_data.region.size));
}
texture = atlas_texture;
} else {
Ref<ArrayMesh> mesh;
mesh.instantiate();
for (int i = 0; i < pack_data.chart_pieces.size(); i++) {
const EditorAtlasPacker::Chart &chart = charts[pack_data.chart_pieces[i]];
Vector<Vector2> vertices;
Vector<int> indices;
Vector<Vector2> uvs;
int vc = chart.vertices.size();
int fc = chart.faces.size();
vertices.resize(vc);
uvs.resize(vc);
indices.resize(fc * 3);
{
Vector2 *vw = vertices.ptrw();
int *iw = indices.ptrw();
Vector2 *uvw = uvs.ptrw();
for (int j = 0; j < vc; j++) {
vw[j] = chart.vertices[j];
Vector2 uv = chart.vertices[j];
if (chart.transposed) {
SWAP(uv.x, uv.y);
}
uv += chart.final_offset;
uv /= new_atlas->get_size(); //normalize uv to 0-1 range
uvw[j] = uv;
}
for (int j = 0; j < fc; j++) {
iw[j * 3 + 0] = chart.faces[j].vertex[0];
iw[j * 3 + 1] = chart.faces[j].vertex[1];
iw[j * 3 + 2] = chart.faces[j].vertex[2];
}
}
Array arrays;
arrays.resize(Mesh::ARRAY_MAX);
arrays[Mesh::ARRAY_VERTEX] = vertices;
arrays[Mesh::ARRAY_TEX_UV] = uvs;
arrays[Mesh::ARRAY_INDEX] = indices;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
}
Ref<MeshTexture> mesh_texture;
mesh_texture.instantiate();
mesh_texture->set_base_texture(cache);
mesh_texture->set_image_size(pack_data.image->get_size());
mesh_texture->set_mesh(mesh);
texture = mesh_texture;
}
String save_path = p_base_paths[E.key] + ".res";
ResourceSaver::save(texture, save_path);
idx++;
}
return OK;
}

View File

@@ -0,0 +1,70 @@
/**************************************************************************/
/* resource_importer_texture_atlas.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. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
#include "core/io/resource_importer.h"
class ResourceImporterTextureAtlas : public ResourceImporter {
GDCLASS(ResourceImporterTextureAtlas, ResourceImporter);
struct PackData {
Rect2 region;
bool is_cropped = false;
bool is_mesh = false;
Vector<int> chart_pieces; //one for region, many for mesh
Vector<Vector<Vector2>> chart_vertices; //for mesh
Ref<Image> image;
};
public:
enum ImportMode {
IMPORT_MODE_REGION,
IMPORT_MODE_2D_MESH
};
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_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 String get_option_group_file() const override;
virtual Error import(ResourceUID::ID p_source_id, 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 Error import_group_file(const String &p_group_file, const HashMap<String, HashMap<StringName, Variant>> &p_source_file_options, const HashMap<String, String> &p_base_paths) override;
virtual bool can_import_threaded() const override { return true; }
};

View File

@@ -0,0 +1,54 @@
/**************************************************************************/
/* resource_importer_texture_settings.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_texture_settings.h"
#include "core/config/project_settings.h"
#include "core/os/os.h"
// ResourceImporterTextureSettings contains code used by
// multiple texture importers and the export dialog.
bool ResourceImporterTextureSettings::should_import_s3tc_bptc() {
if (GLOBAL_GET("rendering/textures/vram_compression/import_s3tc_bptc")) {
return true;
}
// If the project settings override is not enabled, import
// S3TC/BPTC only when the host operating system needs it.
return OS::get_singleton()->get_preferred_texture_format() == OS::PREFERRED_TEXTURE_FORMAT_S3TC_BPTC;
}
bool ResourceImporterTextureSettings::should_import_etc2_astc() {
if (GLOBAL_GET("rendering/textures/vram_compression/import_etc2_astc")) {
return true;
}
// If the project settings override is not enabled, import
// ETC2/ASTC only when the host operating system needs it.
return OS::get_singleton()->get_preferred_texture_format() == OS::PREFERRED_TEXTURE_FORMAT_ETC2_ASTC;
}

View File

@@ -0,0 +1,38 @@
/**************************************************************************/
/* resource_importer_texture_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. */
/**************************************************************************/
#pragma once
// ResourceImporterTextureSettings contains code used by
// multiple texture importers and the export dialog.
namespace ResourceImporterTextureSettings {
bool should_import_s3tc_bptc();
bool should_import_etc2_astc();
} //namespace ResourceImporterTextureSettings

View File

@@ -0,0 +1,100 @@
/**************************************************************************/
/* resource_importer_wav.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_wav.h"
#include "core/io/resource_saver.h"
String ResourceImporterWAV::get_importer_name() const {
return "wav";
}
String ResourceImporterWAV::get_visible_name() const {
return "Microsoft WAV";
}
void ResourceImporterWAV::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("wav");
}
String ResourceImporterWAV::get_save_extension() const {
return "sample";
}
String ResourceImporterWAV::get_resource_type() const {
return "AudioStreamWAV";
}
bool ResourceImporterWAV::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
if (p_option == "force/max_rate_hz" && !bool(p_options["force/max_rate"])) {
return false;
}
// Don't show begin/end loop points if loop mode is auto-detected or disabled.
if ((int)p_options["edit/loop_mode"] < 2 && (p_option == "edit/loop_begin" || p_option == "edit/loop_end")) {
return false;
}
return true;
}
int ResourceImporterWAV::get_preset_count() const {
return 0;
}
String ResourceImporterWAV::get_preset_name(int p_idx) const {
return String();
}
void ResourceImporterWAV::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force/8_bit"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force/mono"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force/max_rate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "force/max_rate_hz", PROPERTY_HINT_RANGE, "11025,192000,1,exp"), 44100));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "edit/trim"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "edit/normalize"), false));
// Keep the `edit/loop_mode` enum in sync with AudioStreamWAV::LoopMode (note: +1 offset due to "Detect From WAV").
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_mode", PROPERTY_HINT_ENUM, "Detect From WAV,Disabled,Forward,Ping-Pong,Backward", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_begin"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_end"), -1));
// Quite OK Audio is lightweight enough and supports virtually every significant AudioStreamWAV feature.
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "PCM (Uncompressed),IMA ADPCM,Quite OK Audio"), 2));
}
Error ResourceImporterWAV::import(ResourceUID::ID p_source_id, 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) {
Dictionary options;
for (const KeyValue<StringName, Variant> &pair : p_options) {
options[pair.key] = pair.value;
}
Ref<AudioStreamWAV> sample = AudioStreamWAV::load_from_file(p_source_file, options);
ResourceSaver::save(sample, p_save_path + ".sample");
return OK;
}

View File

@@ -0,0 +1,55 @@
/**************************************************************************/
/* resource_importer_wav.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. */
/**************************************************************************/
#pragma once
#include "core/io/resource_importer.h"
#include "scene/resources/audio_stream_wav.h"
class ResourceImporterWAV : public ResourceImporter {
GDCLASS(ResourceImporterWAV, 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_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(ResourceUID::ID p_source_id, 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 can_import_threaded() const override { return true; }
};

View File

@@ -0,0 +1,379 @@
/**************************************************************************/
/* unicode_ranges.inc */
/**************************************************************************/
/* 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. */
/**************************************************************************/
// This file was generated using the `misc/scripts/unicode_ranges_fetch.py` script.
#ifndef UNICODE_RANGES_INC
#define UNICODE_RANGES_INC
// Unicode Character Blocks
// Source: https://www.unicode.org/Public/16.0.0/ucd/Blocks.txt
struct UniRange {
int32_t start;
int32_t end;
String name;
};
static UniRange unicode_ranges[] = {
{ 0x0000, 0x007F, U"Basic Latin" },
{ 0x0080, 0x00FF, U"Latin-1 Supplement" },
{ 0x0100, 0x017F, U"Latin Extended-A" },
{ 0x0180, 0x024F, U"Latin Extended-B" },
{ 0x0250, 0x02AF, U"IPA Extensions" },
{ 0x02B0, 0x02FF, U"Spacing Modifier Letters" },
{ 0x0300, 0x036F, U"Combining Diacritical Marks" },
{ 0x0370, 0x03FF, U"Greek and Coptic" },
{ 0x0400, 0x04FF, U"Cyrillic" },
{ 0x0500, 0x052F, U"Cyrillic Supplement" },
{ 0x0530, 0x058F, U"Armenian" },
{ 0x0590, 0x05FF, U"Hebrew" },
{ 0x0600, 0x06FF, U"Arabic" },
{ 0x0700, 0x074F, U"Syriac" },
{ 0x0750, 0x077F, U"Arabic Supplement" },
{ 0x0780, 0x07BF, U"Thaana" },
{ 0x07C0, 0x07FF, U"NKo" },
{ 0x0800, 0x083F, U"Samaritan" },
{ 0x0840, 0x085F, U"Mandaic" },
{ 0x0860, 0x086F, U"Syriac Supplement" },
{ 0x0870, 0x089F, U"Arabic Extended-B" },
{ 0x08A0, 0x08FF, U"Arabic Extended-A" },
{ 0x0900, 0x097F, U"Devanagari" },
{ 0x0980, 0x09FF, U"Bengali" },
{ 0x0A00, 0x0A7F, U"Gurmukhi" },
{ 0x0A80, 0x0AFF, U"Gujarati" },
{ 0x0B00, 0x0B7F, U"Oriya" },
{ 0x0B80, 0x0BFF, U"Tamil" },
{ 0x0C00, 0x0C7F, U"Telugu" },
{ 0x0C80, 0x0CFF, U"Kannada" },
{ 0x0D00, 0x0D7F, U"Malayalam" },
{ 0x0D80, 0x0DFF, U"Sinhala" },
{ 0x0E00, 0x0E7F, U"Thai" },
{ 0x0E80, 0x0EFF, U"Lao" },
{ 0x0F00, 0x0FFF, U"Tibetan" },
{ 0x1000, 0x109F, U"Myanmar" },
{ 0x10A0, 0x10FF, U"Georgian" },
{ 0x1100, 0x11FF, U"Hangul Jamo" },
{ 0x1200, 0x137F, U"Ethiopic" },
{ 0x1380, 0x139F, U"Ethiopic Supplement" },
{ 0x13A0, 0x13FF, U"Cherokee" },
{ 0x1400, 0x167F, U"Unified Canadian Aboriginal Syllabics" },
{ 0x1680, 0x169F, U"Ogham" },
{ 0x16A0, 0x16FF, U"Runic" },
{ 0x1700, 0x171F, U"Tagalog" },
{ 0x1720, 0x173F, U"Hanunoo" },
{ 0x1740, 0x175F, U"Buhid" },
{ 0x1760, 0x177F, U"Tagbanwa" },
{ 0x1780, 0x17FF, U"Khmer" },
{ 0x1800, 0x18AF, U"Mongolian" },
{ 0x18B0, 0x18FF, U"Unified Canadian Aboriginal Syllabics Extended" },
{ 0x1900, 0x194F, U"Limbu" },
{ 0x1950, 0x197F, U"Tai Le" },
{ 0x1980, 0x19DF, U"New Tai Lue" },
{ 0x19E0, 0x19FF, U"Khmer Symbols" },
{ 0x1A00, 0x1A1F, U"Buginese" },
{ 0x1A20, 0x1AAF, U"Tai Tham" },
{ 0x1AB0, 0x1AFF, U"Combining Diacritical Marks Extended" },
{ 0x1B00, 0x1B7F, U"Balinese" },
{ 0x1B80, 0x1BBF, U"Sundanese" },
{ 0x1BC0, 0x1BFF, U"Batak" },
{ 0x1C00, 0x1C4F, U"Lepcha" },
{ 0x1C50, 0x1C7F, U"Ol Chiki" },
{ 0x1C80, 0x1C8F, U"Cyrillic Extended-C" },
{ 0x1C90, 0x1CBF, U"Georgian Extended" },
{ 0x1CC0, 0x1CCF, U"Sundanese Supplement" },
{ 0x1CD0, 0x1CFF, U"Vedic Extensions" },
{ 0x1D00, 0x1D7F, U"Phonetic Extensions" },
{ 0x1D80, 0x1DBF, U"Phonetic Extensions Supplement" },
{ 0x1DC0, 0x1DFF, U"Combining Diacritical Marks Supplement" },
{ 0x1E00, 0x1EFF, U"Latin Extended Additional" },
{ 0x1F00, 0x1FFF, U"Greek Extended" },
{ 0x2000, 0x206F, U"General Punctuation" },
{ 0x2070, 0x209F, U"Superscripts and Subscripts" },
{ 0x20A0, 0x20CF, U"Currency Symbols" },
{ 0x20D0, 0x20FF, U"Combining Diacritical Marks for Symbols" },
{ 0x2100, 0x214F, U"Letterlike Symbols" },
{ 0x2150, 0x218F, U"Number Forms" },
{ 0x2190, 0x21FF, U"Arrows" },
{ 0x2200, 0x22FF, U"Mathematical Operators" },
{ 0x2300, 0x23FF, U"Miscellaneous Technical" },
{ 0x2400, 0x243F, U"Control Pictures" },
{ 0x2440, 0x245F, U"Optical Character Recognition" },
{ 0x2460, 0x24FF, U"Enclosed Alphanumerics" },
{ 0x2500, 0x257F, U"Box Drawing" },
{ 0x2580, 0x259F, U"Block Elements" },
{ 0x25A0, 0x25FF, U"Geometric Shapes" },
{ 0x2600, 0x26FF, U"Miscellaneous Symbols" },
{ 0x2700, 0x27BF, U"Dingbats" },
{ 0x27C0, 0x27EF, U"Miscellaneous Mathematical Symbols-A" },
{ 0x27F0, 0x27FF, U"Supplemental Arrows-A" },
{ 0x2800, 0x28FF, U"Braille Patterns" },
{ 0x2900, 0x297F, U"Supplemental Arrows-B" },
{ 0x2980, 0x29FF, U"Miscellaneous Mathematical Symbols-B" },
{ 0x2A00, 0x2AFF, U"Supplemental Mathematical Operators" },
{ 0x2B00, 0x2BFF, U"Miscellaneous Symbols and Arrows" },
{ 0x2C00, 0x2C5F, U"Glagolitic" },
{ 0x2C60, 0x2C7F, U"Latin Extended-C" },
{ 0x2C80, 0x2CFF, U"Coptic" },
{ 0x2D00, 0x2D2F, U"Georgian Supplement" },
{ 0x2D30, 0x2D7F, U"Tifinagh" },
{ 0x2D80, 0x2DDF, U"Ethiopic Extended" },
{ 0x2DE0, 0x2DFF, U"Cyrillic Extended-A" },
{ 0x2E00, 0x2E7F, U"Supplemental Punctuation" },
{ 0x2E80, 0x2EFF, U"CJK Radicals Supplement" },
{ 0x2F00, 0x2FDF, U"Kangxi Radicals" },
{ 0x2FF0, 0x2FFF, U"Ideographic Description Characters" },
{ 0x3000, 0x303F, U"CJK Symbols and Punctuation" },
{ 0x3040, 0x309F, U"Hiragana" },
{ 0x30A0, 0x30FF, U"Katakana" },
{ 0x3100, 0x312F, U"Bopomofo" },
{ 0x3130, 0x318F, U"Hangul Compatibility Jamo" },
{ 0x3190, 0x319F, U"Kanbun" },
{ 0x31A0, 0x31BF, U"Bopomofo Extended" },
{ 0x31C0, 0x31EF, U"CJK Strokes" },
{ 0x31F0, 0x31FF, U"Katakana Phonetic Extensions" },
{ 0x3200, 0x32FF, U"Enclosed CJK Letters and Months" },
{ 0x3300, 0x33FF, U"CJK Compatibility" },
{ 0x3400, 0x4DBF, U"CJK Unified Ideographs Extension A" },
{ 0x4DC0, 0x4DFF, U"Yijing Hexagram Symbols" },
{ 0x4E00, 0x9FFF, U"CJK Unified Ideographs" },
{ 0xA000, 0xA48F, U"Yi Syllables" },
{ 0xA490, 0xA4CF, U"Yi Radicals" },
{ 0xA4D0, 0xA4FF, U"Lisu" },
{ 0xA500, 0xA63F, U"Vai" },
{ 0xA640, 0xA69F, U"Cyrillic Extended-B" },
{ 0xA6A0, 0xA6FF, U"Bamum" },
{ 0xA700, 0xA71F, U"Modifier Tone Letters" },
{ 0xA720, 0xA7FF, U"Latin Extended-D" },
{ 0xA800, 0xA82F, U"Syloti Nagri" },
{ 0xA830, 0xA83F, U"Common Indic Number Forms" },
{ 0xA840, 0xA87F, U"Phags-pa" },
{ 0xA880, 0xA8DF, U"Saurashtra" },
{ 0xA8E0, 0xA8FF, U"Devanagari Extended" },
{ 0xA900, 0xA92F, U"Kayah Li" },
{ 0xA930, 0xA95F, U"Rejang" },
{ 0xA960, 0xA97F, U"Hangul Jamo Extended-A" },
{ 0xA980, 0xA9DF, U"Javanese" },
{ 0xA9E0, 0xA9FF, U"Myanmar Extended-B" },
{ 0xAA00, 0xAA5F, U"Cham" },
{ 0xAA60, 0xAA7F, U"Myanmar Extended-A" },
{ 0xAA80, 0xAADF, U"Tai Viet" },
{ 0xAAE0, 0xAAFF, U"Meetei Mayek Extensions" },
{ 0xAB00, 0xAB2F, U"Ethiopic Extended-A" },
{ 0xAB30, 0xAB6F, U"Latin Extended-E" },
{ 0xAB70, 0xABBF, U"Cherokee Supplement" },
{ 0xABC0, 0xABFF, U"Meetei Mayek" },
{ 0xAC00, 0xD7AF, U"Hangul Syllables" },
{ 0xD7B0, 0xD7FF, U"Hangul Jamo Extended-B" },
{ 0xE000, 0xF8FF, U"Private Use Area" },
{ 0xF900, 0xFAFF, U"CJK Compatibility Ideographs" },
{ 0xFB00, 0xFB4F, U"Alphabetic Presentation Forms" },
{ 0xFB50, 0xFDFF, U"Arabic Presentation Forms-A" },
{ 0xFE10, 0xFE1F, U"Vertical Forms" },
{ 0xFE20, 0xFE2F, U"Combining Half Marks" },
{ 0xFE30, 0xFE4F, U"CJK Compatibility Forms" },
{ 0xFE50, 0xFE6F, U"Small Form Variants" },
{ 0xFE70, 0xFEFF, U"Arabic Presentation Forms-B" },
{ 0xFF00, 0xFFEF, U"Halfwidth and Fullwidth Forms" },
{ 0x10000, 0x1007F, U"Linear B Syllabary" },
{ 0x10080, 0x100FF, U"Linear B Ideograms" },
{ 0x10100, 0x1013F, U"Aegean Numbers" },
{ 0x10140, 0x1018F, U"Ancient Greek Numbers" },
{ 0x10190, 0x101CF, U"Ancient Symbols" },
{ 0x101D0, 0x101FF, U"Phaistos Disc" },
{ 0x10280, 0x1029F, U"Lycian" },
{ 0x102A0, 0x102DF, U"Carian" },
{ 0x102E0, 0x102FF, U"Coptic Epact Numbers" },
{ 0x10300, 0x1032F, U"Old Italic" },
{ 0x10330, 0x1034F, U"Gothic" },
{ 0x10350, 0x1037F, U"Old Permic" },
{ 0x10380, 0x1039F, U"Ugaritic" },
{ 0x103A0, 0x103DF, U"Old Persian" },
{ 0x10400, 0x1044F, U"Deseret" },
{ 0x10450, 0x1047F, U"Shavian" },
{ 0x10480, 0x104AF, U"Osmanya" },
{ 0x104B0, 0x104FF, U"Osage" },
{ 0x10500, 0x1052F, U"Elbasan" },
{ 0x10530, 0x1056F, U"Caucasian Albanian" },
{ 0x10570, 0x105BF, U"Vithkuqi" },
{ 0x105C0, 0x105FF, U"Todhri" },
{ 0x10600, 0x1077F, U"Linear A" },
{ 0x10780, 0x107BF, U"Latin Extended-F" },
{ 0x10800, 0x1083F, U"Cypriot Syllabary" },
{ 0x10840, 0x1085F, U"Imperial Aramaic" },
{ 0x10860, 0x1087F, U"Palmyrene" },
{ 0x10880, 0x108AF, U"Nabataean" },
{ 0x108E0, 0x108FF, U"Hatran" },
{ 0x10900, 0x1091F, U"Phoenician" },
{ 0x10920, 0x1093F, U"Lydian" },
{ 0x10980, 0x1099F, U"Meroitic Hieroglyphs" },
{ 0x109A0, 0x109FF, U"Meroitic Cursive" },
{ 0x10A00, 0x10A5F, U"Kharoshthi" },
{ 0x10A60, 0x10A7F, U"Old South Arabian" },
{ 0x10A80, 0x10A9F, U"Old North Arabian" },
{ 0x10AC0, 0x10AFF, U"Manichaean" },
{ 0x10B00, 0x10B3F, U"Avestan" },
{ 0x10B40, 0x10B5F, U"Inscriptional Parthian" },
{ 0x10B60, 0x10B7F, U"Inscriptional Pahlavi" },
{ 0x10B80, 0x10BAF, U"Psalter Pahlavi" },
{ 0x10C00, 0x10C4F, U"Old Turkic" },
{ 0x10C80, 0x10CFF, U"Old Hungarian" },
{ 0x10D00, 0x10D3F, U"Hanifi Rohingya" },
{ 0x10D40, 0x10D8F, U"Garay" },
{ 0x10E60, 0x10E7F, U"Rumi Numeral Symbols" },
{ 0x10E80, 0x10EBF, U"Yezidi" },
{ 0x10EC0, 0x10EFF, U"Arabic Extended-C" },
{ 0x10F00, 0x10F2F, U"Old Sogdian" },
{ 0x10F30, 0x10F6F, U"Sogdian" },
{ 0x10F70, 0x10FAF, U"Old Uyghur" },
{ 0x10FB0, 0x10FDF, U"Chorasmian" },
{ 0x10FE0, 0x10FFF, U"Elymaic" },
{ 0x11000, 0x1107F, U"Brahmi" },
{ 0x11080, 0x110CF, U"Kaithi" },
{ 0x110D0, 0x110FF, U"Sora Sompeng" },
{ 0x11100, 0x1114F, U"Chakma" },
{ 0x11150, 0x1117F, U"Mahajani" },
{ 0x11180, 0x111DF, U"Sharada" },
{ 0x111E0, 0x111FF, U"Sinhala Archaic Numbers" },
{ 0x11200, 0x1124F, U"Khojki" },
{ 0x11280, 0x112AF, U"Multani" },
{ 0x112B0, 0x112FF, U"Khudawadi" },
{ 0x11300, 0x1137F, U"Grantha" },
{ 0x11380, 0x113FF, U"Tulu-Tigalari" },
{ 0x11400, 0x1147F, U"Newa" },
{ 0x11480, 0x114DF, U"Tirhuta" },
{ 0x11580, 0x115FF, U"Siddham" },
{ 0x11600, 0x1165F, U"Modi" },
{ 0x11660, 0x1167F, U"Mongolian Supplement" },
{ 0x11680, 0x116CF, U"Takri" },
{ 0x116D0, 0x116FF, U"Myanmar Extended-C" },
{ 0x11700, 0x1174F, U"Ahom" },
{ 0x11800, 0x1184F, U"Dogra" },
{ 0x118A0, 0x118FF, U"Warang Citi" },
{ 0x11900, 0x1195F, U"Dives Akuru" },
{ 0x119A0, 0x119FF, U"Nandinagari" },
{ 0x11A00, 0x11A4F, U"Zanabazar Square" },
{ 0x11A50, 0x11AAF, U"Soyombo" },
{ 0x11AB0, 0x11ABF, U"Unified Canadian Aboriginal Syllabics Extended-A" },
{ 0x11AC0, 0x11AFF, U"Pau Cin Hau" },
{ 0x11B00, 0x11B5F, U"Devanagari Extended-A" },
{ 0x11BC0, 0x11BFF, U"Sunuwar" },
{ 0x11C00, 0x11C6F, U"Bhaiksuki" },
{ 0x11C70, 0x11CBF, U"Marchen" },
{ 0x11D00, 0x11D5F, U"Masaram Gondi" },
{ 0x11D60, 0x11DAF, U"Gunjala Gondi" },
{ 0x11EE0, 0x11EFF, U"Makasar" },
{ 0x11F00, 0x11F5F, U"Kawi" },
{ 0x11FB0, 0x11FBF, U"Lisu Supplement" },
{ 0x11FC0, 0x11FFF, U"Tamil Supplement" },
{ 0x12000, 0x123FF, U"Cuneiform" },
{ 0x12400, 0x1247F, U"Cuneiform Numbers and Punctuation" },
{ 0x12480, 0x1254F, U"Early Dynastic Cuneiform" },
{ 0x12F90, 0x12FFF, U"Cypro-Minoan" },
{ 0x13000, 0x1342F, U"Egyptian Hieroglyphs" },
{ 0x13460, 0x143FF, U"Egyptian Hieroglyphs Extended-A" },
{ 0x14400, 0x1467F, U"Anatolian Hieroglyphs" },
{ 0x16100, 0x1613F, U"Gurung Khema" },
{ 0x16800, 0x16A3F, U"Bamum Supplement" },
{ 0x16A40, 0x16A6F, U"Mro" },
{ 0x16A70, 0x16ACF, U"Tangsa" },
{ 0x16AD0, 0x16AFF, U"Bassa Vah" },
{ 0x16B00, 0x16B8F, U"Pahawh Hmong" },
{ 0x16D40, 0x16D7F, U"Kirat Rai" },
{ 0x16E40, 0x16E9F, U"Medefaidrin" },
{ 0x16F00, 0x16F9F, U"Miao" },
{ 0x16FE0, 0x16FFF, U"Ideographic Symbols and Punctuation" },
{ 0x17000, 0x187FF, U"Tangut" },
{ 0x18800, 0x18AFF, U"Tangut Components" },
{ 0x18B00, 0x18CFF, U"Khitan Small Script" },
{ 0x18D00, 0x18D7F, U"Tangut Supplement" },
{ 0x1AFF0, 0x1AFFF, U"Kana Extended-B" },
{ 0x1B000, 0x1B0FF, U"Kana Supplement" },
{ 0x1B100, 0x1B12F, U"Kana Extended-A" },
{ 0x1B130, 0x1B16F, U"Small Kana Extension" },
{ 0x1B170, 0x1B2FF, U"Nushu" },
{ 0x1BC00, 0x1BC9F, U"Duployan" },
{ 0x1BCA0, 0x1BCAF, U"Shorthand Format Controls" },
{ 0x1CC00, 0x1CEBF, U"Symbols for Legacy Computing Supplement" },
{ 0x1CF00, 0x1CFCF, U"Znamenny Musical Notation" },
{ 0x1D000, 0x1D0FF, U"Byzantine Musical Symbols" },
{ 0x1D100, 0x1D1FF, U"Musical Symbols" },
{ 0x1D200, 0x1D24F, U"Ancient Greek Musical Notation" },
{ 0x1D2C0, 0x1D2DF, U"Kaktovik Numerals" },
{ 0x1D2E0, 0x1D2FF, U"Mayan Numerals" },
{ 0x1D300, 0x1D35F, U"Tai Xuan Jing Symbols" },
{ 0x1D360, 0x1D37F, U"Counting Rod Numerals" },
{ 0x1D400, 0x1D7FF, U"Mathematical Alphanumeric Symbols" },
{ 0x1D800, 0x1DAAF, U"Sutton SignWriting" },
{ 0x1DF00, 0x1DFFF, U"Latin Extended-G" },
{ 0x1E000, 0x1E02F, U"Glagolitic Supplement" },
{ 0x1E030, 0x1E08F, U"Cyrillic Extended-D" },
{ 0x1E100, 0x1E14F, U"Nyiakeng Puachue Hmong" },
{ 0x1E290, 0x1E2BF, U"Toto" },
{ 0x1E2C0, 0x1E2FF, U"Wancho" },
{ 0x1E4D0, 0x1E4FF, U"Nag Mundari" },
{ 0x1E5D0, 0x1E5FF, U"Ol Onal" },
{ 0x1E7E0, 0x1E7FF, U"Ethiopic Extended-B" },
{ 0x1E800, 0x1E8DF, U"Mende Kikakui" },
{ 0x1E900, 0x1E95F, U"Adlam" },
{ 0x1EC70, 0x1ECBF, U"Indic Siyaq Numbers" },
{ 0x1ED00, 0x1ED4F, U"Ottoman Siyaq Numbers" },
{ 0x1EE00, 0x1EEFF, U"Arabic Mathematical Alphabetic Symbols" },
{ 0x1F000, 0x1F02F, U"Mahjong Tiles" },
{ 0x1F030, 0x1F09F, U"Domino Tiles" },
{ 0x1F0A0, 0x1F0FF, U"Playing Cards" },
{ 0x1F100, 0x1F1FF, U"Enclosed Alphanumeric Supplement" },
{ 0x1F200, 0x1F2FF, U"Enclosed Ideographic Supplement" },
{ 0x1F300, 0x1F5FF, U"Miscellaneous Symbols and Pictographs" },
{ 0x1F600, 0x1F64F, U"Emoticons" },
{ 0x1F650, 0x1F67F, U"Ornamental Dingbats" },
{ 0x1F680, 0x1F6FF, U"Transport and Map Symbols" },
{ 0x1F700, 0x1F77F, U"Alchemical Symbols" },
{ 0x1F780, 0x1F7FF, U"Geometric Shapes Extended" },
{ 0x1F800, 0x1F8FF, U"Supplemental Arrows-C" },
{ 0x1F900, 0x1F9FF, U"Supplemental Symbols and Pictographs" },
{ 0x1FA00, 0x1FA6F, U"Chess Symbols" },
{ 0x1FA70, 0x1FAFF, U"Symbols and Pictographs Extended-A" },
{ 0x1FB00, 0x1FBFF, U"Symbols for Legacy Computing" },
{ 0x20000, 0x2A6DF, U"CJK Unified Ideographs Extension B" },
{ 0x2A700, 0x2B73F, U"CJK Unified Ideographs Extension C" },
{ 0x2B740, 0x2B81F, U"CJK Unified Ideographs Extension D" },
{ 0x2B820, 0x2CEAF, U"CJK Unified Ideographs Extension E" },
{ 0x2CEB0, 0x2EBEF, U"CJK Unified Ideographs Extension F" },
{ 0x2EBF0, 0x2EE5F, U"CJK Unified Ideographs Extension I" },
{ 0x2F800, 0x2FA1F, U"CJK Compatibility Ideographs Supplement" },
{ 0x30000, 0x3134F, U"CJK Unified Ideographs Extension G" },
{ 0x31350, 0x323AF, U"CJK Unified Ideographs Extension H" },
{ 0xF0000, 0xFFFFF, U"Supplementary Private Use Area-A" },
{ 0x100000, 0x10FFFF, U"Supplementary Private Use Area-B" },
{ 0x10FFFF, 0x10FFFF, String() }
};
#endif // UNICODE_RANGES_INC