feat: godot-engine-source-4.3-stable

This commit is contained in:
Jan van der Weide 2025-01-17 16:36:38 +01:00
parent c59a7dcade
commit 7125d019b5
11149 changed files with 5070401 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,577 @@
/**************************************************************************/
/* collada.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef COLLADA_H
#define COLLADA_H
#include "core/config/project_settings.h"
#include "core/io/xml_parser.h"
#include "core/templates/rb_map.h"
#include "scene/resources/material.h"
class Collada {
public:
enum ImportFlags {
IMPORT_FLAG_SCENE = 1,
IMPORT_FLAG_ANIMATION = 2
};
struct Image {
String path;
};
struct Material {
String name;
String instance_effect;
};
struct Effect {
String name;
HashMap<String, Variant> params;
struct Channel {
int uv_idx = 0;
String texture;
Color color;
Channel() {}
};
Channel diffuse, specular, emission, bump;
float shininess = 40;
bool found_double_sided = false;
bool double_sided = true;
bool unshaded = false;
String get_texture_path(const String &p_source, Collada &p_state) const;
Effect() {
diffuse.color = Color(1, 1, 1, 1);
}
};
struct CameraData {
enum Mode {
MODE_PERSPECTIVE,
MODE_ORTHOGONAL
};
Mode mode = MODE_PERSPECTIVE;
union {
struct {
float x_fov = 0;
float y_fov = 0;
} perspective;
struct {
float x_mag = 0;
float y_mag = 0;
} orthogonal;
};
float aspect = 1;
float z_near = 0.05;
float z_far = 4000;
CameraData() {}
};
struct LightData {
enum Mode {
MODE_AMBIENT,
MODE_DIRECTIONAL,
MODE_OMNI,
MODE_SPOT
};
Mode mode = MODE_AMBIENT;
Color color = Color(1, 1, 1, 1);
float constant_att = 0;
float linear_att = 0;
float quad_att = 0;
float spot_angle = 45;
float spot_exp = 1;
LightData() {}
};
struct MeshData {
String name;
struct Source {
Vector<float> array;
int stride = 0;
};
HashMap<String, Source> sources;
struct Vertices {
HashMap<String, String> sources;
};
HashMap<String, Vertices> vertices;
struct Primitives {
struct SourceRef {
String source;
int offset = 0;
};
String material;
HashMap<String, SourceRef> sources;
Vector<float> polygons;
Vector<float> indices;
int count = 0;
int vertex_size = 0;
};
Vector<Primitives> primitives;
bool found_double_sided = false;
bool double_sided = true;
MeshData() {}
};
struct CurveData {
String name;
bool closed = false;
struct Source {
Vector<String> sarray;
Vector<float> array;
int stride = 0;
};
HashMap<String, Source> sources;
HashMap<String, String> control_vertices;
CurveData() {}
};
struct SkinControllerData {
String base;
bool use_idrefs = false;
Transform3D bind_shape;
struct Source {
Vector<String> sarray; //maybe for names
Vector<float> array;
int stride = 1;
Source() {}
};
HashMap<String, Source> sources;
struct Joints {
HashMap<String, String> sources;
} joints;
struct Weights {
struct SourceRef {
String source;
int offset = 0;
};
String material;
HashMap<String, SourceRef> sources;
Vector<float> sets;
Vector<float> indices;
int count = 0;
} weights;
HashMap<String, Transform3D> bone_rest_map;
SkinControllerData() {}
};
struct MorphControllerData {
String mesh;
String mode;
struct Source {
int stride = 1;
Vector<String> sarray; //maybe for names
Vector<float> array;
Source() {}
};
HashMap<String, Source> sources;
HashMap<String, String> targets;
MorphControllerData() {}
};
struct Vertex {
int idx = 0;
Vector3 vertex;
Vector3 normal;
Vector3 uv;
Vector3 uv2;
Plane tangent;
Color color;
int uid = 0;
struct Weight {
int bone_idx = 0;
float weight = 0;
bool operator<(const Weight w) const { return weight > w.weight; } //heaviest first
};
Vector<Weight> weights;
void fix_weights() {
weights.sort();
if (weights.size() > 4) {
//cap to 4 and make weights add up 1
weights.resize(4);
float total = 0;
for (int i = 0; i < 4; i++) {
total += weights[i].weight;
}
if (total) {
for (int i = 0; i < 4; i++) {
weights.write[i].weight /= total;
}
}
}
}
void fix_unit_scale(const Collada &p_state);
bool operator<(const Vertex &p_vert) const {
if (uid == p_vert.uid) {
if (vertex == p_vert.vertex) {
if (normal == p_vert.normal) {
if (uv == p_vert.uv) {
if (uv2 == p_vert.uv2) {
if (!weights.is_empty() || !p_vert.weights.is_empty()) {
if (weights.size() == p_vert.weights.size()) {
for (int i = 0; i < weights.size(); i++) {
if (weights[i].bone_idx != p_vert.weights[i].bone_idx) {
return weights[i].bone_idx < p_vert.weights[i].bone_idx;
}
if (weights[i].weight != p_vert.weights[i].weight) {
return weights[i].weight < p_vert.weights[i].weight;
}
}
} else {
return weights.size() < p_vert.weights.size();
}
}
return (color < p_vert.color);
} else {
return (uv2 < p_vert.uv2);
}
} else {
return (uv < p_vert.uv);
}
} else {
return (normal < p_vert.normal);
}
} else {
return vertex < p_vert.vertex;
}
} else {
return uid < p_vert.uid;
}
}
Vertex() {}
};
struct Node {
enum Type {
TYPE_NODE,
TYPE_JOINT,
TYPE_SKELETON, //this bone is not collada, it's added afterwards as optimization
TYPE_LIGHT,
TYPE_CAMERA,
TYPE_GEOMETRY
};
struct XForm {
enum Op {
OP_ROTATE,
OP_SCALE,
OP_TRANSLATE,
OP_MATRIX,
OP_VISIBILITY
};
String id;
Op op = OP_ROTATE;
Vector<float> data;
};
Type type = TYPE_NODE;
String name;
String id;
String empty_draw_type;
bool noname = false;
Vector<XForm> xform_list;
Transform3D default_transform;
Transform3D post_transform;
Vector<Node *> children;
Node *parent = nullptr;
Transform3D compute_transform(const Collada &p_state) const;
Transform3D get_global_transform() const;
Transform3D get_transform() const;
bool ignore_anim = false;
Node() {}
virtual ~Node() {
for (int i = 0; i < children.size(); i++) {
memdelete(children[i]);
}
};
};
struct NodeSkeleton : public Node {
NodeSkeleton() { type = TYPE_SKELETON; }
};
struct NodeJoint : public Node {
NodeSkeleton *owner = nullptr;
String sid;
NodeJoint() {
type = TYPE_JOINT;
}
};
struct NodeGeometry : public Node {
bool controller = false;
String source;
struct Material {
String target;
};
HashMap<String, Material> material_map;
Vector<String> skeletons;
NodeGeometry() { type = TYPE_GEOMETRY; }
};
struct NodeCamera : public Node {
String camera;
NodeCamera() { type = TYPE_CAMERA; }
};
struct NodeLight : public Node {
String light;
NodeLight() { type = TYPE_LIGHT; }
};
struct VisualScene {
String name;
Vector<Node *> root_nodes;
~VisualScene() {
for (int i = 0; i < root_nodes.size(); i++) {
memdelete(root_nodes[i]);
}
}
};
struct AnimationClip {
String name;
float begin = 0;
float end = 1;
Vector<String> tracks;
AnimationClip() {}
};
struct AnimationTrack {
String id;
String target;
String param;
String component;
bool property = false;
enum InterpolationType {
INTERP_LINEAR,
INTERP_BEZIER
};
struct Key {
enum Type {
TYPE_FLOAT,
TYPE_MATRIX
};
float time = 0;
Vector<float> data;
Point2 in_tangent;
Point2 out_tangent;
InterpolationType interp_type = INTERP_LINEAR;
Key() {}
};
Vector<float> get_value_at_time(float p_time) const;
Vector<Key> keys;
AnimationTrack() {}
};
/****************/
/* IMPORT STATE */
/****************/
struct State {
int import_flags = 0;
float unit_scale = 1.0;
Vector3::Axis up_axis = Vector3::AXIS_Y;
bool z_up = false;
struct Version {
int major = 0, minor = 0, rev = 0;
bool operator<(const Version &p_ver) const { return (major == p_ver.major) ? ((minor == p_ver.minor) ? (rev < p_ver.rev) : minor < p_ver.minor) : major < p_ver.major; }
Version(int p_major = 0, int p_minor = 0, int p_rev = 0) {
major = p_major;
minor = p_minor;
rev = p_rev;
}
} version;
HashMap<String, CameraData> camera_data_map;
HashMap<String, MeshData> mesh_data_map;
HashMap<String, LightData> light_data_map;
HashMap<String, CurveData> curve_data_map;
HashMap<String, String> mesh_name_map;
HashMap<String, String> morph_name_map;
HashMap<String, String> morph_ownership_map;
HashMap<String, SkinControllerData> skin_controller_data_map;
HashMap<String, MorphControllerData> morph_controller_data_map;
HashMap<String, Image> image_map;
HashMap<String, Material> material_map;
HashMap<String, Effect> effect_map;
HashMap<String, VisualScene> visual_scene_map;
HashMap<String, Node *> scene_map;
HashSet<String> idref_joints;
HashMap<String, String> sid_to_node_map;
//RBMap<String,NodeJoint*> bone_map;
HashMap<String, Transform3D> bone_rest_map;
String local_path;
String root_visual_scene;
String root_physics_scene;
Vector<AnimationClip> animation_clips;
Vector<AnimationTrack> animation_tracks;
HashMap<String, Vector<int>> referenced_tracks;
HashMap<String, Vector<int>> by_id_tracks;
float animation_length = 0;
State() {}
} state;
Error load(const String &p_path, int p_flags = 0);
Collada();
Transform3D fix_transform(const Transform3D &p_transform);
Transform3D get_root_transform() const;
int get_uv_channel(const String &p_name);
private: // private stuff
HashMap<String, int> channel_map;
void _parse_asset(XMLParser &p_parser);
void _parse_image(XMLParser &p_parser);
void _parse_material(XMLParser &p_parser);
void _parse_effect_material(XMLParser &p_parser, Effect &p_effect, String &p_id);
void _parse_effect(XMLParser &p_parser);
void _parse_camera(XMLParser &p_parser);
void _parse_light(XMLParser &p_parser);
void _parse_animation_clip(XMLParser &p_parser);
void _parse_mesh_geometry(XMLParser &p_parser, const String &p_id, const String &p_name);
void _parse_curve_geometry(XMLParser &p_parser, const String &p_id, const String &p_name);
void _parse_skin_controller(XMLParser &p_parser, const String &p_id);
void _parse_morph_controller(XMLParser &p_parser, const String &p_id);
void _parse_controller(XMLParser &p_parser);
Node *_parse_visual_instance_geometry(XMLParser &p_parser);
Node *_parse_visual_instance_camera(XMLParser &p_parser);
Node *_parse_visual_instance_light(XMLParser &p_parser);
Node *_parse_visual_node_instance_data(XMLParser &p_parser);
Node *_parse_visual_scene_node(XMLParser &p_parser);
void _parse_visual_scene(XMLParser &p_parser);
void _parse_animation(XMLParser &p_parser);
void _parse_scene(XMLParser &p_parser);
void _parse_library(XMLParser &p_parser);
Variant _parse_param(XMLParser &p_parser);
Vector<float> _read_float_array(XMLParser &p_parser);
Vector<String> _read_string_array(XMLParser &p_parser);
Transform3D _read_transform(XMLParser &p_parser);
String _read_empty_draw_type(XMLParser &p_parser);
void _joint_set_owner(Collada::Node *p_node, NodeSkeleton *p_owner);
void _create_skeletons(Collada::Node **p_node, NodeSkeleton *p_skeleton = nullptr);
void _find_morph_nodes(VisualScene *p_vscene, Node *p_node);
bool _remove_node(Node *p_parent, Node *p_node);
void _remove_node(VisualScene *p_vscene, Node *p_node);
void _merge_skeletons2(VisualScene *p_vscene);
void _merge_skeletons(VisualScene *p_vscene, Node *p_node);
bool _optimize_skeletons(VisualScene *p_vscene, Node *p_node);
bool _move_geometry_to_skeletons(VisualScene *p_vscene, Node *p_node, List<Node *> *p_mgeom);
void _optimize();
};
#endif // COLLADA_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,47 @@
/**************************************************************************/
/* editor_import_collada.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef EDITOR_IMPORT_COLLADA_H
#define EDITOR_IMPORT_COLLADA_H
#include "editor/import/3d/resource_importer_scene.h"
class EditorSceneFormatImporterCollada : public EditorSceneFormatImporter {
GDCLASS(EditorSceneFormatImporterCollada, EditorSceneFormatImporter);
public:
virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List<String> *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps = nullptr, Error *r_err = nullptr) override;
EditorSceneFormatImporterCollada();
};
#endif // EDITOR_IMPORT_COLLADA_H

View file

@ -0,0 +1,251 @@
/**************************************************************************/
/* post_import_plugin_skeleton_renamer.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "post_import_plugin_skeleton_renamer.h"
#include "editor/import/3d/scene_import_settings.h"
#include "scene/3d/bone_attachment_3d.h"
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/animation/animation_player.h"
#include "scene/resources/bone_map.h"
void PostImportPluginSkeletonRenamer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/rename_bones"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/unique_node/make_unique"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::STRING, "retarget/bone_renamer/unique_node/skeleton_name"), "GeneralSkeleton"));
}
}
void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options, const HashMap<String, String> &p_rename_map) {
// Prepare objects.
Object *map = p_options["retarget/bone_map"].get_validated_object();
if (!map || !bool(p_options["retarget/bone_renamer/rename_bones"])) {
return;
}
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
if (skeleton) {
// Rename bones in Skeleton3D.
int len = skeleton->get_bone_count();
for (int i = 0; i < len; i++) {
String current_bone_name = skeleton->get_bone_name(i);
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
if (new_bone_name) {
skeleton->set_bone_name(i, new_bone_name->value);
}
}
}
// Rename bones in Skin.
{
TypedArray<Node> nodes = p_base_scene->find_children("*", "ImporterMeshInstance3D");
while (nodes.size()) {
ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(nodes.pop_back());
Ref<Skin> skin = mi->get_skin();
if (skin.is_valid()) {
Node *node = mi->get_node(mi->get_skeleton_path());
if (node) {
Skeleton3D *mesh_skeleton = Object::cast_to<Skeleton3D>(node);
if (mesh_skeleton && node == skeleton) {
int len = skin->get_bind_count();
for (int i = 0; i < len; i++) {
String current_bone_name = skin->get_bind_name(i);
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
if (new_bone_name) {
skin->set_bind_name(i, new_bone_name->value);
}
}
}
}
}
}
}
// Rename bones in AnimationPlayer.
{
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
List<StringName> anims;
ap->get_animation_list(&anims);
for (const StringName &name : anims) {
Ref<Animation> anim = ap->get_animation(name);
int len = anim->get_track_count();
for (int i = 0; i < len; i++) {
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
continue;
}
String track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
if (node) {
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (track_skeleton && track_skeleton == skeleton) {
String current_bone_name = anim->track_get_path(i).get_subname(0);
const HashMap<String, String>::ConstIterator new_bone_name = p_rename_map.find(current_bone_name);
if (new_bone_name) {
String new_track_path = track_path + ":" + new_bone_name->value;
anim->track_set_path(i, new_track_path);
}
}
}
}
}
}
}
// Rename bones in all Nodes by calling method.
{
Dictionary rename_map_dict;
for (HashMap<String, String>::ConstIterator E = p_rename_map.begin(); E; ++E) {
rename_map_dict[E->key] = E->value;
}
TypedArray<Node> nodes = p_base_scene->find_children("*", "BoneAttachment3D");
while (nodes.size()) {
BoneAttachment3D *attachment = Object::cast_to<BoneAttachment3D>(nodes.pop_back());
if (attachment) {
attachment->notify_skeleton_bones_renamed(p_base_scene, skeleton, rename_map_dict);
}
}
}
}
void PostImportPluginSkeletonRenamer::internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
// Prepare objects.
Object *map = p_options["retarget/bone_map"].get_validated_object();
if (!map || !bool(p_options["retarget/bone_renamer/rename_bones"])) {
return;
}
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
BoneMap *bone_map = Object::cast_to<BoneMap>(map);
int len = skeleton->get_bone_count();
// First, prepare main rename map.
HashMap<String, String> main_rename_map;
for (int i = 0; i < len; i++) {
String bone_name = skeleton->get_bone_name(i);
String target_name = bone_map->find_profile_bone_name(bone_name);
if (target_name.is_empty()) {
continue;
}
main_rename_map.insert(bone_name, target_name);
}
// Preprocess of renaming bones to avoid to conflict with original bone name.
HashMap<String, String> pre_rename_map; // HashMap<skeleton bone name, target(profile) bone name>
{
Vector<String> solved_name_stack;
for (int i = 0; i < len; i++) {
String bone_name = skeleton->get_bone_name(i);
String target_name = bone_map->find_profile_bone_name(bone_name);
if (target_name.is_empty() || bone_name == target_name || skeleton->find_bone(target_name) == -1) {
continue; // No conflicting.
}
// Solve conflicting.
Ref<SkeletonProfile> profile = bone_map->get_profile();
String solved_name = target_name;
for (int j = 2; skeleton->find_bone(solved_name) >= 0 || profile->find_bone(solved_name) >= 0 || solved_name_stack.has(solved_name); j++) {
solved_name = target_name + itos(j);
}
solved_name_stack.push_back(solved_name);
pre_rename_map.insert(target_name, solved_name);
}
_internal_process(p_category, p_base_scene, p_node, p_resource, p_options, pre_rename_map);
}
// Main process of renaming bones.
{
// Apply pre-renaming result to prepared main rename map.
Vector<String> remove_queue;
for (HashMap<String, String>::Iterator E = main_rename_map.begin(); E; ++E) {
if (pre_rename_map.has(E->key)) {
remove_queue.push_back(E->key);
}
}
for (int i = 0; i < remove_queue.size(); i++) {
main_rename_map.insert(pre_rename_map[remove_queue[i]], main_rename_map[remove_queue[i]]);
main_rename_map.erase(remove_queue[i]);
}
_internal_process(p_category, p_base_scene, p_node, p_resource, p_options, main_rename_map);
}
// Make unique skeleton.
if (bool(p_options["retarget/bone_renamer/unique_node/make_unique"])) {
String unique_name = String(p_options["retarget/bone_renamer/unique_node/skeleton_name"]);
ERR_FAIL_COND_MSG(unique_name.is_empty(), "Skeleton unique name cannot be empty.");
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
List<StringName> anims;
ap->get_animation_list(&anims);
for (const StringName &name : anims) {
Ref<Animation> anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
String track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *orig_node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
while (node) {
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (track_skeleton && track_skeleton == skeleton) {
if (node == orig_node) {
if (anim->track_get_path(i).get_subname_count() > 0) {
anim->track_set_path(i, UNIQUE_NODE_PREFIX + unique_name + String(":") + anim->track_get_path(i).get_concatenated_subnames());
} else {
anim->track_set_path(i, UNIQUE_NODE_PREFIX + unique_name);
}
} else {
if (anim->track_get_path(i).get_subname_count() > 0) {
anim->track_set_path(i, UNIQUE_NODE_PREFIX + unique_name + "/" + node->get_path_to(orig_node) + String(":") + anim->track_get_path(i).get_concatenated_subnames());
} else {
anim->track_set_path(i, UNIQUE_NODE_PREFIX + unique_name + "/" + node->get_path_to(orig_node));
}
}
break;
}
node = node->get_parent();
}
}
}
}
skeleton->set_name(unique_name);
skeleton->set_unique_name_in_owner(true);
}
}
}
PostImportPluginSkeletonRenamer::PostImportPluginSkeletonRenamer() {
}

View file

@ -0,0 +1,48 @@
/**************************************************************************/
/* post_import_plugin_skeleton_renamer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef POST_IMPORT_PLUGIN_SKELETON_RENAMER_H
#define POST_IMPORT_PLUGIN_SKELETON_RENAMER_H
#include "resource_importer_scene.h"
class PostImportPluginSkeletonRenamer : public EditorScenePostImportPlugin {
GDCLASS(PostImportPluginSkeletonRenamer, EditorScenePostImportPlugin);
public:
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) override;
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) override;
void _internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options, const HashMap<String, String> &p_rename_map);
PostImportPluginSkeletonRenamer();
};
#endif // POST_IMPORT_PLUGIN_SKELETON_RENAMER_H

View file

@ -0,0 +1,777 @@
/**************************************************************************/
/* post_import_plugin_skeleton_rest_fixer.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "post_import_plugin_skeleton_rest_fixer.h"
#include "editor/import/3d/scene_import_settings.h"
#include "scene/3d/bone_attachment_3d.h"
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/animation/animation_player.h"
#include "scene/resources/bone_map.h"
void PostImportPluginSkeletonRestFixer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/apply_node_transforms"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/normalize_position_tracks"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/reset_all_bone_poses_after_import"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/overwrite_axis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/keep_global_rest_on_leftovers"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/rest_fixer/fix_silhouette/enable", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
// TODO: PostImportPlugin need to be implemented such as validate_option(PropertyInfo &property, const Dictionary &p_options).
// get_internal_option_visibility() is not sufficient because it can only retrieve options implemented in the core and can only read option values.
// r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/filter", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::STRING_NAME, PROPERTY_HINT_ENUM, "Hips,Spine,Chest")), Array()));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/fix_silhouette/filter", PROPERTY_HINT_ARRAY_TYPE, "StringName"), Array()));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "retarget/rest_fixer/fix_silhouette/threshold"), 15));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "retarget/rest_fixer/fix_silhouette/base_height_adjustment", PROPERTY_HINT_RANGE, "-1,1,0.01"), 0.0));
}
}
Variant PostImportPluginSkeletonRestFixer::get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
if (p_option.begins_with("retarget/rest_fixer/fix_silhouette/")) {
if (!bool(p_options["retarget/rest_fixer/fix_silhouette/enable"])) {
if (!p_option.ends_with("enable")) {
return false;
}
}
} else if (p_option == "retarget/rest_fixer/keep_global_rest_on_leftovers") {
return bool(p_options["retarget/rest_fixer/overwrite_axis"]);
}
}
return true;
}
void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
// Prepare objects.
Object *map = p_options["retarget/bone_map"].get_validated_object();
if (!map) {
return;
}
BoneMap *bone_map = Object::cast_to<BoneMap>(map);
Ref<SkeletonProfile> profile = bone_map->get_profile();
if (!profile.is_valid()) {
return;
}
Skeleton3D *src_skeleton = Object::cast_to<Skeleton3D>(p_node);
if (!src_skeleton) {
return;
}
bool is_renamed = bool(p_options["retarget/bone_renamer/rename_bones"]);
Array filter = p_options["retarget/rest_fixer/fix_silhouette/filter"];
bool is_rest_changed = false;
// Build profile skeleton.
Skeleton3D *prof_skeleton = memnew(Skeleton3D);
{
int prof_bone_len = profile->get_bone_size();
// Add single bones.
for (int i = 0; i < prof_bone_len; i++) {
prof_skeleton->add_bone(profile->get_bone_name(i));
prof_skeleton->set_bone_rest(i, profile->get_reference_pose(i));
}
// Set parents.
for (int i = 0; i < prof_bone_len; i++) {
int parent = profile->find_bone(profile->get_bone_parent(i));
if (parent >= 0) {
prof_skeleton->set_bone_parent(i, parent);
}
}
}
// Get global transform.
Transform3D global_transform;
if (bool(p_options["retarget/rest_fixer/apply_node_transforms"])) {
Node *pr = src_skeleton;
while (pr) {
Node3D *pr3d = Object::cast_to<Node3D>(pr);
if (pr3d) {
global_transform = pr3d->get_transform() * global_transform;
pr3d->set_transform(Transform3D());
}
pr = pr->get_parent();
}
global_transform.origin = Vector3(); // Translation by a Node is not a bone animation, so the retargeted model should be at the origin.
}
// Apply node transforms.
if (bool(p_options["retarget/rest_fixer/apply_node_transforms"])) {
Vector3 scl = global_transform.basis.get_scale_global();
Vector<int> bones_to_process = src_skeleton->get_parentless_bones();
for (int i = 0; i < bones_to_process.size(); i++) {
src_skeleton->set_bone_rest(bones_to_process[i], global_transform.orthonormalized() * src_skeleton->get_bone_rest(bones_to_process[i]));
src_skeleton->set_bone_pose_position(bones_to_process[i], global_transform.orthonormalized().xform(src_skeleton->get_bone_pose_position(bones_to_process[i])));
src_skeleton->set_bone_pose_rotation(bones_to_process[i], global_transform.basis.get_rotation_quaternion() * src_skeleton->get_bone_pose_rotation(bones_to_process[i]));
src_skeleton->set_bone_pose_scale(bones_to_process[i], (global_transform.orthonormalized().basis * Basis().scaled(src_skeleton->get_bone_pose_scale((bones_to_process[i])))).get_scale());
}
while (bones_to_process.size() > 0) {
int src_idx = bones_to_process[0];
bones_to_process.erase(src_idx);
Vector<int> src_children = src_skeleton->get_bone_children(src_idx);
for (int i = 0; i < src_children.size(); i++) {
bones_to_process.push_back(src_children[i]);
}
src_skeleton->set_bone_rest(src_idx, Transform3D(src_skeleton->get_bone_rest(src_idx).basis, src_skeleton->get_bone_rest(src_idx).origin * scl));
src_skeleton->set_bone_pose_position(src_idx, src_skeleton->get_bone_pose_position(src_idx) * scl);
}
// Fix animation.
bones_to_process = src_skeleton->get_parentless_bones();
{
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
List<StringName> anims;
ap->get_animation_list(&anims);
for (const StringName &name : anims) {
Ref<Animation> anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
continue;
}
if (anim->track_is_compressed(i)) {
continue; // Shouldn't occur in internal_process().
}
String track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
ERR_CONTINUE(!node);
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (!track_skeleton || track_skeleton != src_skeleton) {
continue;
}
StringName bn = anim->track_get_path(i).get_subname(0);
if (!bn) {
continue;
}
int bone_idx = src_skeleton->find_bone(bn);
int key_len = anim->track_get_key_count(i);
if (anim->track_get_type(i) == Animation::TYPE_POSITION_3D) {
if (bones_to_process.has(bone_idx)) {
for (int j = 0; j < key_len; j++) {
Vector3 ps = static_cast<Vector3>(anim->track_get_key_value(i, j));
anim->track_set_key_value(i, j, global_transform.basis.xform(ps) + global_transform.origin);
}
} else {
for (int j = 0; j < key_len; j++) {
Vector3 ps = static_cast<Vector3>(anim->track_get_key_value(i, j));
anim->track_set_key_value(i, j, ps * scl);
}
}
} else if (bones_to_process.has(bone_idx)) {
if (anim->track_get_type(i) == Animation::TYPE_ROTATION_3D) {
for (int j = 0; j < key_len; j++) {
Quaternion qt = static_cast<Quaternion>(anim->track_get_key_value(i, j));
anim->track_set_key_value(i, j, global_transform.basis.get_rotation_quaternion() * qt);
}
} else {
for (int j = 0; j < key_len; j++) {
Basis sc = Basis().scaled(static_cast<Vector3>(anim->track_get_key_value(i, j)));
anim->track_set_key_value(i, j, (global_transform.orthonormalized().basis * sc).get_scale());
}
}
}
}
}
}
}
is_rest_changed = true;
}
// Complement Rotation track for compatibility between different rests.
{
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
List<StringName> anims;
ap->get_animation_list(&anims);
for (const StringName &name : anims) {
Ref<Animation> anim = ap->get_animation(name);
int track_len = anim->get_track_count();
// Detect does the animation have skeleton's TRS track.
String track_path;
bool found_skeleton = false;
for (int i = 0; i < track_len; i++) {
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
continue;
}
track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
if (node) {
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (track_skeleton && track_skeleton == src_skeleton) {
found_skeleton = true;
break;
}
}
}
if (!found_skeleton) {
continue;
}
// Search and insert rot track if it doesn't exist.
for (int prof_idx = 0; prof_idx < prof_skeleton->get_bone_count(); prof_idx++) {
String bone_name = is_renamed ? prof_skeleton->get_bone_name(prof_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_idx)));
if (bone_name.is_empty()) {
continue;
}
int src_idx = src_skeleton->find_bone(bone_name);
if (src_idx == -1) {
continue;
}
String insert_path = track_path + ":" + bone_name;
int rot_track = anim->find_track(insert_path, Animation::TYPE_ROTATION_3D);
if (rot_track == -1) {
int track = anim->add_track(Animation::TYPE_ROTATION_3D);
anim->track_set_path(track, insert_path);
anim->track_set_imported(track, true);
anim->rotation_track_insert_key(track, 0, src_skeleton->get_bone_rest(src_idx).basis.get_rotation_quaternion());
}
}
}
}
}
// Fix silhouette.
Vector<Transform3D> silhouette_diff; // Transform values to be ignored when overwrite axis.
silhouette_diff.resize(src_skeleton->get_bone_count());
Transform3D *silhouette_diff_w = silhouette_diff.ptrw();
LocalVector<Transform3D> pre_silhouette_skeleton_global_rest;
for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
pre_silhouette_skeleton_global_rest.push_back(src_skeleton->get_bone_global_rest(i));
}
if (bool(p_options["retarget/rest_fixer/fix_silhouette/enable"])) {
Vector<int> bones_to_process = prof_skeleton->get_parentless_bones();
while (bones_to_process.size() > 0) {
int prof_idx = bones_to_process[0];
bones_to_process.erase(prof_idx);
Vector<int> prof_children = prof_skeleton->get_bone_children(prof_idx);
for (int i = 0; i < prof_children.size(); i++) {
bones_to_process.push_back(prof_children[i]);
}
// Calc virtual/looking direction with origins.
bool is_filtered = false;
for (int i = 0; i < filter.size(); i++) {
if (String(filter[i]) == prof_skeleton->get_bone_name(prof_idx)) {
is_filtered = true;
break;
}
}
if (is_filtered) {
continue;
}
int src_idx = src_skeleton->find_bone(is_renamed ? prof_skeleton->get_bone_name(prof_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_idx))));
if (src_idx < 0 || profile->get_tail_direction(prof_idx) == SkeletonProfile::TAIL_DIRECTION_END) {
continue;
}
Vector3 prof_tail;
Vector3 src_tail;
if (profile->get_tail_direction(prof_idx) == SkeletonProfile::TAIL_DIRECTION_AVERAGE_CHILDREN) {
PackedInt32Array prof_bone_children = prof_skeleton->get_bone_children(prof_idx);
int children_size = prof_bone_children.size();
if (children_size == 0) {
continue;
}
bool exist_all_children = true;
for (int i = 0; i < children_size; i++) {
int prof_child_idx = prof_bone_children[i];
int src_child_idx = src_skeleton->find_bone(is_renamed ? prof_skeleton->get_bone_name(prof_child_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_child_idx))));
if (src_child_idx < 0) {
exist_all_children = false;
break;
}
prof_tail = prof_tail + prof_skeleton->get_bone_global_rest(prof_child_idx).origin;
src_tail = src_tail + src_skeleton->get_bone_global_rest(src_child_idx).origin;
}
if (!exist_all_children) {
continue;
}
prof_tail = prof_tail / children_size;
src_tail = src_tail / children_size;
}
if (profile->get_tail_direction(prof_idx) == SkeletonProfile::TAIL_DIRECTION_SPECIFIC_CHILD) {
int prof_tail_idx = prof_skeleton->find_bone(profile->get_bone_tail(prof_idx));
if (prof_tail_idx < 0) {
continue;
}
int src_tail_idx = src_skeleton->find_bone(is_renamed ? prof_skeleton->get_bone_name(prof_tail_idx) : String(bone_map->get_skeleton_bone_name(prof_skeleton->get_bone_name(prof_tail_idx))));
if (src_tail_idx < 0) {
continue;
}
prof_tail = prof_skeleton->get_bone_global_rest(prof_tail_idx).origin;
src_tail = src_skeleton->get_bone_global_rest(src_tail_idx).origin;
}
Vector3 prof_head = prof_skeleton->get_bone_global_rest(prof_idx).origin;
Vector3 src_head = src_skeleton->get_bone_global_rest(src_idx).origin;
Vector3 prof_dir = prof_tail - prof_head;
Vector3 src_dir = src_tail - src_head;
// Rotate rest.
if (Math::abs(Math::rad_to_deg(src_dir.angle_to(prof_dir))) > float(p_options["retarget/rest_fixer/fix_silhouette/threshold"])) {
// Get rotation difference.
Vector3 up_vec; // Need to rotate other than roll axis.
switch (Vector3(abs(src_dir.x), abs(src_dir.y), abs(src_dir.z)).min_axis_index()) {
case Vector3::AXIS_X: {
up_vec = Vector3(1, 0, 0);
} break;
case Vector3::AXIS_Y: {
up_vec = Vector3(0, 1, 0);
} break;
case Vector3::AXIS_Z: {
up_vec = Vector3(0, 0, 1);
} break;
}
Basis src_b;
src_b = src_b.looking_at(src_dir, up_vec);
Basis prof_b;
prof_b = src_b.looking_at(prof_dir, up_vec);
if (prof_b.is_equal_approx(Basis())) {
continue; // May not need to rotate.
}
Basis diff_b = prof_b * src_b.inverse();
// Apply rotation difference as global transform to skeleton.
Basis src_pg;
int src_parent = src_skeleton->get_bone_parent(src_idx);
if (src_parent >= 0) {
src_pg = src_skeleton->get_bone_global_rest(src_parent).basis;
}
Transform3D fixed_rest = Transform3D(src_pg.inverse() * diff_b * src_pg * src_skeleton->get_bone_rest(src_idx).basis, src_skeleton->get_bone_rest(src_idx).origin);
src_skeleton->set_bone_rest(src_idx, fixed_rest);
}
}
// Adjust scale base bone height.
float base_adjustment = float(p_options["retarget/rest_fixer/fix_silhouette/base_height_adjustment"]);
if (!Math::is_zero_approx(base_adjustment)) {
StringName scale_base_bone_name = profile->get_scale_base_bone();
int src_bone_idx = src_skeleton->find_bone(scale_base_bone_name);
Transform3D src_rest = src_skeleton->get_bone_rest(src_bone_idx);
src_skeleton->set_bone_rest(src_bone_idx, Transform3D(src_rest.basis, Vector3(src_rest.origin.x, src_rest.origin.y + base_adjustment, src_rest.origin.z)));
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
List<StringName> anims;
ap->get_animation_list(&anims);
for (const StringName &name : anims) {
Ref<Animation> anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
if (anim->track_get_path(i).get_subname_count() != 1 || anim->track_get_type(i) != Animation::TYPE_POSITION_3D) {
continue;
}
if (anim->track_is_compressed(i)) {
continue; // Shouldn't occur in internal_process().
}
String track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
ERR_CONTINUE(!node);
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (!track_skeleton || track_skeleton != src_skeleton) {
continue;
}
StringName bn = anim->track_get_path(i).get_concatenated_subnames();
if (bn != scale_base_bone_name) {
continue;
}
int key_len = anim->track_get_key_count(i);
for (int j = 0; j < key_len; j++) {
Vector3 pos = static_cast<Vector3>(anim->track_get_key_value(i, j));
pos.y += base_adjustment;
anim->track_set_key_value(i, j, pos);
}
}
}
}
}
// For skin modification in overwrite rest.
for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
silhouette_diff_w[i] = pre_silhouette_skeleton_global_rest[i] * src_skeleton->get_bone_global_rest(i).affine_inverse();
}
is_rest_changed = true;
}
// Set motion scale to Skeleton if normalize position tracks.
if (bool(p_options["retarget/rest_fixer/normalize_position_tracks"])) {
int src_bone_idx = src_skeleton->find_bone(profile->get_scale_base_bone());
if (src_bone_idx >= 0) {
real_t motion_scale = abs(src_skeleton->get_bone_global_rest(src_bone_idx).origin.y);
if (motion_scale > 0) {
src_skeleton->set_motion_scale(motion_scale);
}
}
}
// Overwrite axis.
if (bool(p_options["retarget/rest_fixer/overwrite_axis"])) {
LocalVector<Transform3D> old_skeleton_rest;
LocalVector<Transform3D> old_skeleton_global_rest;
for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
old_skeleton_rest.push_back(src_skeleton->get_bone_rest(i));
old_skeleton_global_rest.push_back(src_skeleton->get_bone_global_rest(i));
}
bool keep_global_rest_leftovers = bool(p_options["retarget/rest_fixer/keep_global_rest_on_leftovers"]);
// Scan hierarchy and populate a whitelist of unmapped bones without mapped descendants.
Vector<int> keep_bone_rest;
if (keep_global_rest_leftovers) {
Vector<int> bones_to_process = src_skeleton->get_parentless_bones();
while (bones_to_process.size() > 0) {
int src_idx = bones_to_process[0];
bones_to_process.erase(src_idx);
Vector<int> src_children = src_skeleton->get_bone_children(src_idx);
for (const int &src_child : src_children) {
bones_to_process.push_back(src_child);
}
StringName src_bone_name = is_renamed ? StringName(src_skeleton->get_bone_name(src_idx)) : bone_map->find_profile_bone_name(src_skeleton->get_bone_name(src_idx));
if (src_bone_name != StringName() && !profile->has_bone(src_bone_name)) {
// Scan descendants for mapped bones.
bool found_mapped = false;
Vector<int> decendants_to_process = src_skeleton->get_bone_children(src_idx);
while (decendants_to_process.size() > 0) {
int desc_idx = decendants_to_process[0];
decendants_to_process.erase(desc_idx);
Vector<int> desc_children = src_skeleton->get_bone_children(desc_idx);
for (const int &desc_child : desc_children) {
decendants_to_process.push_back(desc_child);
}
StringName desc_bone_name = is_renamed ? StringName(src_skeleton->get_bone_name(desc_idx)) : bone_map->find_profile_bone_name(src_skeleton->get_bone_name(desc_idx));
if (desc_bone_name != StringName() && profile->has_bone(desc_bone_name)) {
found_mapped = true;
break;
}
}
if (!found_mapped) {
keep_bone_rest.push_back(src_idx); // No mapped descendants. Add to whitelist.
}
}
}
}
Vector<Basis> diffs;
diffs.resize(src_skeleton->get_bone_count());
Basis *diffs_w = diffs.ptrw();
Vector<int> bones_to_process = src_skeleton->get_parentless_bones();
while (bones_to_process.size() > 0) {
int src_idx = bones_to_process[0];
bones_to_process.erase(src_idx);
Vector<int> src_children = src_skeleton->get_bone_children(src_idx);
for (int i = 0; i < src_children.size(); i++) {
bones_to_process.push_back(src_children[i]);
}
Basis tgt_rot;
StringName src_bone_name = is_renamed ? StringName(src_skeleton->get_bone_name(src_idx)) : bone_map->find_profile_bone_name(src_skeleton->get_bone_name(src_idx));
if (src_bone_name != StringName()) {
Basis src_pg;
int src_parent_idx = src_skeleton->get_bone_parent(src_idx);
if (src_parent_idx >= 0) {
src_pg = src_skeleton->get_bone_global_rest(src_parent_idx).basis;
}
int prof_idx = profile->find_bone(src_bone_name);
if (prof_idx >= 0) {
tgt_rot = src_pg.inverse() * prof_skeleton->get_bone_global_rest(prof_idx).basis; // Mapped bone uses reference pose.
} else if (keep_global_rest_leftovers && keep_bone_rest.has(src_idx)) {
tgt_rot = src_pg.inverse() * old_skeleton_global_rest[src_idx].basis; // Non-Mapped bone without mapped children keeps global rest.
}
}
if (src_skeleton->get_bone_parent(src_idx) >= 0) {
diffs_w[src_idx] = tgt_rot.inverse() * diffs[src_skeleton->get_bone_parent(src_idx)] * src_skeleton->get_bone_rest(src_idx).basis;
} else {
diffs_w[src_idx] = tgt_rot.inverse() * src_skeleton->get_bone_rest(src_idx).basis;
}
Basis diff;
if (src_skeleton->get_bone_parent(src_idx) >= 0) {
diff = diffs[src_skeleton->get_bone_parent(src_idx)];
}
src_skeleton->set_bone_rest(src_idx, Transform3D(tgt_rot, diff.xform(src_skeleton->get_bone_rest(src_idx).origin)));
}
// Fix animation.
{
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
ERR_CONTINUE(!ap);
List<StringName> anims;
ap->get_animation_list(&anims);
for (const StringName &name : anims) {
Ref<Animation> anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
continue;
}
if (anim->track_is_compressed(i)) {
continue; // Shouldn't occur in internal_process().
}
String track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
ERR_CONTINUE(!node);
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (!track_skeleton || track_skeleton != src_skeleton) {
continue;
}
StringName bn = anim->track_get_path(i).get_subname(0);
if (!bn) {
continue;
}
int bone_idx = src_skeleton->find_bone(bn);
Transform3D old_rest = old_skeleton_rest[bone_idx];
Transform3D new_rest = src_skeleton->get_bone_rest(bone_idx);
Transform3D old_pg;
Transform3D new_pg;
int parent_idx = src_skeleton->get_bone_parent(bone_idx);
if (parent_idx >= 0) {
old_pg = old_skeleton_global_rest[parent_idx];
new_pg = src_skeleton->get_bone_global_rest(parent_idx);
}
int key_len = anim->track_get_key_count(i);
if (anim->track_get_type(i) == Animation::TYPE_ROTATION_3D) {
Quaternion old_rest_q = old_rest.basis.get_rotation_quaternion();
Quaternion new_rest_q = new_rest.basis.get_rotation_quaternion();
Quaternion old_pg_q = old_pg.basis.get_rotation_quaternion();
Quaternion new_pg_q = new_pg.basis.get_rotation_quaternion();
for (int j = 0; j < key_len; j++) {
Quaternion qt = static_cast<Quaternion>(anim->track_get_key_value(i, j));
anim->track_set_key_value(i, j, new_pg_q.inverse() * old_pg_q * qt * old_rest_q.inverse() * old_pg_q.inverse() * new_pg_q * new_rest_q);
}
} else if (anim->track_get_type(i) == Animation::TYPE_SCALE_3D) {
Basis old_rest_b_inv = old_rest.basis.inverse();
Basis new_rest_b = new_rest.basis;
Basis old_pg_b = old_pg.basis;
Basis new_pg_b = new_pg.basis;
Basis old_pg_b_inv = old_pg.basis.inverse();
Basis new_pg_b_inv = new_pg.basis.inverse();
for (int j = 0; j < key_len; j++) {
Basis sc = Basis().scaled(static_cast<Vector3>(anim->track_get_key_value(i, j)));
anim->track_set_key_value(i, j, (new_pg_b_inv * old_pg_b * sc * old_rest_b_inv * old_pg_b_inv * new_pg_b * new_rest_b).get_scale());
}
} else {
Vector3 old_rest_o = old_rest.origin;
Vector3 new_rest_o = new_rest.origin;
Basis old_pg_b = old_pg.basis;
Basis new_pg_b = new_pg.basis;
for (int j = 0; j < key_len; j++) {
Vector3 ps = static_cast<Vector3>(anim->track_get_key_value(i, j));
anim->track_set_key_value(i, j, new_pg_b.xform_inv(old_pg_b.xform(ps - old_rest_o)) + new_rest_o);
}
}
}
}
}
}
if (p_options.has("retarget/rest_fixer/reset_all_bone_poses_after_import") && !bool(p_options["retarget/rest_fixer/reset_all_bone_poses_after_import"])) {
// If Reset All Bone Poses After Import is disabled, preserve the original bone pose, adjusted for the new bone rolls.
for (int bone_idx = 0; bone_idx < src_skeleton->get_bone_count(); bone_idx++) {
Transform3D old_rest = old_skeleton_rest[bone_idx];
Transform3D new_rest = src_skeleton->get_bone_rest(bone_idx);
Transform3D old_pg;
Transform3D new_pg;
int parent_idx = src_skeleton->get_bone_parent(bone_idx);
if (parent_idx >= 0) {
old_pg = old_skeleton_global_rest[parent_idx];
new_pg = src_skeleton->get_bone_global_rest(parent_idx);
}
Quaternion old_pg_q = old_pg.basis.get_rotation_quaternion();
Quaternion new_pg_q = new_pg.basis.get_rotation_quaternion();
Quaternion qt = src_skeleton->get_bone_pose_rotation(bone_idx);
src_skeleton->set_bone_pose_rotation(bone_idx, new_pg_q.inverse() * old_pg_q * qt * old_rest.basis.get_rotation_quaternion().inverse() * old_pg_q.inverse() * new_pg_q * new_rest.basis.get_rotation_quaternion());
Basis sc = Basis().scaled(src_skeleton->get_bone_pose_scale(bone_idx));
src_skeleton->set_bone_pose_scale(bone_idx, (new_pg.basis.inverse() * old_pg.basis * sc * old_rest.basis.inverse() * old_pg.basis.inverse() * new_pg.basis * new_rest.basis).get_scale());
Vector3 ps = src_skeleton->get_bone_pose_position(bone_idx);
src_skeleton->set_bone_pose_position(bone_idx, new_pg_q.xform_inv(old_pg_q.xform(ps - old_rest.origin)) + new_rest.origin);
}
}
is_rest_changed = true;
}
// Scale position tracks by motion scale if normalize position tracks.
if (bool(p_options["retarget/rest_fixer/normalize_position_tracks"])) {
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
List<StringName> anims;
ap->get_animation_list(&anims);
for (const StringName &name : anims) {
Ref<Animation> anim = ap->get_animation(name);
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
if (anim->track_get_path(i).get_subname_count() != 1 || anim->track_get_type(i) != Animation::TYPE_POSITION_3D) {
continue;
}
if (anim->track_is_compressed(i)) {
continue; // Shouldn't occur in internal_process().
}
String track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
ERR_CONTINUE(!node);
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (!track_skeleton || track_skeleton != src_skeleton) {
continue;
}
real_t mlt = 1 / src_skeleton->get_motion_scale();
int key_len = anim->track_get_key_count(i);
for (int j = 0; j < key_len; j++) {
Vector3 pos = static_cast<Vector3>(anim->track_get_key_value(i, j));
anim->track_set_key_value(i, j, pos * mlt);
}
}
}
}
}
if (is_rest_changed) {
// Fix skin.
{
HashSet<Ref<Skin>> mutated_skins;
TypedArray<Node> nodes = p_base_scene->find_children("*", "ImporterMeshInstance3D");
while (nodes.size()) {
ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(nodes.pop_back());
ERR_CONTINUE(!mi);
Ref<Skin> skin = mi->get_skin();
if (skin.is_null()) {
continue;
}
if (mutated_skins.has(skin)) {
continue;
}
mutated_skins.insert(skin);
Node *node = mi->get_node(mi->get_skeleton_path());
ERR_CONTINUE(!node);
Skeleton3D *mesh_skeleton = Object::cast_to<Skeleton3D>(node);
if (!mesh_skeleton || mesh_skeleton != src_skeleton) {
continue;
}
int skin_len = skin->get_bind_count();
for (int i = 0; i < skin_len; i++) {
StringName bn = skin->get_bind_name(i);
int bone_idx = src_skeleton->find_bone(bn);
if (bone_idx >= 0) {
Transform3D adjust_transform = src_skeleton->get_bone_global_rest(bone_idx).affine_inverse() * silhouette_diff[bone_idx].affine_inverse() * pre_silhouette_skeleton_global_rest[bone_idx];
adjust_transform.scale(global_transform.basis.get_scale_global());
skin->set_bind_pose(i, adjust_transform * skin->get_bind_pose(i));
}
}
}
nodes = src_skeleton->get_children();
while (nodes.size()) {
BoneAttachment3D *attachment = Object::cast_to<BoneAttachment3D>(nodes.pop_back());
if (attachment == nullptr) {
continue;
}
int bone_idx = attachment->get_bone_idx();
if (bone_idx == -1) {
bone_idx = src_skeleton->find_bone(attachment->get_bone_name());
}
ERR_CONTINUE(bone_idx < 0 || bone_idx >= src_skeleton->get_bone_count());
Transform3D adjust_transform = src_skeleton->get_bone_global_rest(bone_idx).affine_inverse() * silhouette_diff[bone_idx].affine_inverse() * pre_silhouette_skeleton_global_rest[bone_idx];
adjust_transform.scale(global_transform.basis.get_scale_global());
TypedArray<Node> child_nodes = attachment->get_children();
while (child_nodes.size()) {
Node3D *child = Object::cast_to<Node3D>(child_nodes.pop_back());
if (child == nullptr) {
continue;
}
child->set_transform(adjust_transform * child->get_transform());
}
}
}
if (!p_options.has("retarget/rest_fixer/reset_all_bone_poses_after_import") || bool(p_options["retarget/rest_fixer/reset_all_bone_poses_after_import"])) {
// Init skeleton pose to new rest.
for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
Transform3D fixed_rest = src_skeleton->get_bone_rest(i);
src_skeleton->set_bone_pose_position(i, fixed_rest.origin);
src_skeleton->set_bone_pose_rotation(i, fixed_rest.basis.get_rotation_quaternion());
src_skeleton->set_bone_pose_scale(i, fixed_rest.basis.get_scale());
}
}
}
memdelete(prof_skeleton);
}
}
PostImportPluginSkeletonRestFixer::PostImportPluginSkeletonRestFixer() {
}

View file

@ -0,0 +1,47 @@
/**************************************************************************/
/* post_import_plugin_skeleton_rest_fixer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef POST_IMPORT_PLUGIN_SKELETON_REST_FIXER_H
#define POST_IMPORT_PLUGIN_SKELETON_REST_FIXER_H
#include "resource_importer_scene.h"
class PostImportPluginSkeletonRestFixer : public EditorScenePostImportPlugin {
GDCLASS(PostImportPluginSkeletonRestFixer, EditorScenePostImportPlugin);
public:
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) override;
virtual Variant get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) override;
PostImportPluginSkeletonRestFixer();
};
#endif // POST_IMPORT_PLUGIN_SKELETON_REST_FIXER_H

View file

@ -0,0 +1,127 @@
/**************************************************************************/
/* post_import_plugin_skeleton_track_organizer.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "post_import_plugin_skeleton_track_organizer.h"
#include "editor/import/3d/scene_import_settings.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/animation/animation_player.h"
#include "scene/resources/bone_map.h"
void PostImportPluginSkeletonTrackOrganizer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/except_bone_transform"), false));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/unimportant_positions"), true));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/remove_tracks/unmapped_bones"), false));
}
}
void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
// Prepare objects.
Object *map = p_options["retarget/bone_map"].get_validated_object();
if (!map) {
return;
}
BoneMap *bone_map = Object::cast_to<BoneMap>(map);
Ref<SkeletonProfile> profile = bone_map->get_profile();
if (!profile.is_valid()) {
return;
}
Skeleton3D *src_skeleton = Object::cast_to<Skeleton3D>(p_node);
if (!src_skeleton) {
return;
}
bool remove_except_bone = bool(p_options["retarget/remove_tracks/except_bone_transform"]);
bool remove_positions = bool(p_options["retarget/remove_tracks/unimportant_positions"]);
bool remove_unmapped_bones = bool(p_options["retarget/remove_tracks/unmapped_bones"]);
if (!remove_positions && !remove_unmapped_bones) {
return;
}
TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
while (nodes.size()) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
List<StringName> anims;
ap->get_animation_list(&anims);
for (const StringName &name : anims) {
Ref<Animation> anim = ap->get_animation(name);
int track_len = anim->get_track_count();
Vector<int> remove_indices;
for (int i = 0; i < track_len; i++) {
String track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
if (!node) {
if (remove_except_bone) {
remove_indices.push_back(i);
}
continue;
}
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (track_skeleton && track_skeleton == src_skeleton) {
if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
if (remove_except_bone) {
remove_indices.push_back(i);
}
continue;
}
StringName bn = anim->track_get_path(i).get_subname(0);
if (bn) {
int prof_idx = profile->find_bone(bone_map->find_profile_bone_name(bn));
if (remove_unmapped_bones && prof_idx < 0) {
remove_indices.push_back(i);
continue;
}
if (remove_positions && anim->track_get_type(i) == Animation::TYPE_POSITION_3D && prof_idx >= 0) {
StringName prof_bn = profile->get_bone_name(prof_idx);
if (prof_bn == profile->get_root_bone() || prof_bn == profile->get_scale_base_bone()) {
continue;
}
remove_indices.push_back(i);
}
}
}
if (remove_except_bone) {
remove_indices.push_back(i);
}
}
remove_indices.reverse();
for (int i = 0; i < remove_indices.size(); i++) {
anim->remove_track(remove_indices[i]);
}
}
}
}
}
PostImportPluginSkeletonTrackOrganizer::PostImportPluginSkeletonTrackOrganizer() {
}

View file

@ -0,0 +1,46 @@
/**************************************************************************/
/* post_import_plugin_skeleton_track_organizer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef POST_IMPORT_PLUGIN_SKELETON_TRACK_ORGANIZER_H
#define POST_IMPORT_PLUGIN_SKELETON_TRACK_ORGANIZER_H
#include "resource_importer_scene.h"
class PostImportPluginSkeletonTrackOrganizer : public EditorScenePostImportPlugin {
GDCLASS(PostImportPluginSkeletonTrackOrganizer, EditorScenePostImportPlugin);
public:
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) override;
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) override;
PostImportPluginSkeletonTrackOrganizer();
};
#endif // POST_IMPORT_PLUGIN_SKELETON_TRACK_ORGANIZER_H

View file

@ -0,0 +1,614 @@
/**************************************************************************/
/* resource_importer_obj.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "resource_importer_obj.h"
#include "core/io/file_access.h"
#include "core/io/resource_saver.h"
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/resources/3d/importer_mesh.h"
#include "scene/resources/mesh.h"
#include "scene/resources/surface_tool.h"
uint32_t EditorOBJImporter::get_import_flags() const {
return IMPORT_SCENE;
}
static Error _parse_material_library(const String &p_path, HashMap<String, Ref<StandardMaterial3D>> &material_map, List<String> *r_missing_deps) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Couldn't open MTL file '%s', it may not exist or not be readable.", p_path));
Ref<StandardMaterial3D> current;
String current_name;
String base_path = p_path.get_base_dir();
while (true) {
String l = f->get_line().strip_edges();
if (l.begins_with("newmtl ")) {
//vertex
current_name = l.replace("newmtl", "").strip_edges();
current.instantiate();
current->set_name(current_name);
material_map[current_name] = current;
} else if (l.begins_with("Ka ")) {
//uv
WARN_PRINT("OBJ: Ambient light for material '" + current_name + "' is ignored in PBR");
} else if (l.begins_with("Kd ")) {
//normal
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
Vector<String> v = l.split(" ", false);
ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
Color c = current->get_albedo();
c.r = v[1].to_float();
c.g = v[2].to_float();
c.b = v[3].to_float();
current->set_albedo(c);
} else if (l.begins_with("Ks ")) {
//normal
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
Vector<String> v = l.split(" ", false);
ERR_FAIL_COND_V(v.size() < 4, ERR_INVALID_DATA);
float r = v[1].to_float();
float g = v[2].to_float();
float b = v[3].to_float();
float metalness = MAX(r, MAX(g, b));
current->set_metallic(metalness);
} else if (l.begins_with("Ns ")) {
//normal
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
Vector<String> v = l.split(" ", false);
ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA);
float s = v[1].to_float();
current->set_metallic((1000.0 - s) / 1000.0);
} else if (l.begins_with("d ")) {
//normal
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
Vector<String> v = l.split(" ", false);
ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA);
float d = v[1].to_float();
Color c = current->get_albedo();
c.a = d;
current->set_albedo(c);
if (c.a < 0.99) {
current->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
}
} else if (l.begins_with("Tr ")) {
//normal
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
Vector<String> v = l.split(" ", false);
ERR_FAIL_COND_V(v.size() != 2, ERR_INVALID_DATA);
float d = v[1].to_float();
Color c = current->get_albedo();
c.a = 1.0 - d;
current->set_albedo(c);
if (c.a < 0.99) {
current->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
}
} else if (l.begins_with("map_Ka ")) {
//uv
WARN_PRINT("OBJ: Ambient light texture for material '" + current_name + "' is ignored in PBR");
} else if (l.begins_with("map_Kd ")) {
//normal
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
String p = l.replace("map_Kd", "").replace("\\", "/").strip_edges();
String path;
if (p.is_absolute_path()) {
path = p;
} else {
path = base_path.path_join(p);
}
Ref<Texture2D> texture = ResourceLoader::load(path);
if (texture.is_valid()) {
current->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, texture);
} else if (r_missing_deps) {
r_missing_deps->push_back(path);
}
} else if (l.begins_with("map_Ks ")) {
//normal
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
String p = l.replace("map_Ks", "").replace("\\", "/").strip_edges();
String path;
if (p.is_absolute_path()) {
path = p;
} else {
path = base_path.path_join(p);
}
Ref<Texture2D> texture = ResourceLoader::load(path);
if (texture.is_valid()) {
current->set_texture(StandardMaterial3D::TEXTURE_METALLIC, texture);
} else if (r_missing_deps) {
r_missing_deps->push_back(path);
}
} else if (l.begins_with("map_Ns ")) {
//normal
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
String p = l.replace("map_Ns", "").replace("\\", "/").strip_edges();
String path;
if (p.is_absolute_path()) {
path = p;
} else {
path = base_path.path_join(p);
}
Ref<Texture2D> texture = ResourceLoader::load(path);
if (texture.is_valid()) {
current->set_texture(StandardMaterial3D::TEXTURE_ROUGHNESS, texture);
} else if (r_missing_deps) {
r_missing_deps->push_back(path);
}
} else if (l.begins_with("map_bump ")) {
//normal
ERR_FAIL_COND_V(current.is_null(), ERR_FILE_CORRUPT);
String p = l.replace("map_bump", "").replace("\\", "/").strip_edges();
String path = base_path.path_join(p);
Ref<Texture2D> texture = ResourceLoader::load(path);
if (texture.is_valid()) {
current->set_feature(StandardMaterial3D::FEATURE_NORMAL_MAPPING, true);
current->set_texture(StandardMaterial3D::TEXTURE_NORMAL, texture);
} else if (r_missing_deps) {
r_missing_deps->push_back(path);
}
} else if (f->eof_reached()) {
break;
}
}
return OK;
}
static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, bool p_single_mesh, bool p_generate_tangents, bool p_optimize, Vector3 p_scale_mesh, Vector3 p_offset_mesh, bool p_disable_compression, List<String> *r_missing_deps) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Couldn't open OBJ file '%s', it may not exist or not be readable.", p_path));
// Avoid trying to load/interpret potential build artifacts from Visual Studio (e.g. when compiling native plugins inside the project tree)
// This should only match, if it's indeed a COFF file header
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types
const int first_bytes = f->get_16();
static const Vector<int> coff_header_machines{
0x0, // IMAGE_FILE_MACHINE_UNKNOWN
0x8664, // IMAGE_FILE_MACHINE_AMD64
0x1c0, // IMAGE_FILE_MACHINE_ARM
0x14c, // IMAGE_FILE_MACHINE_I386
0x200, // IMAGE_FILE_MACHINE_IA64
};
ERR_FAIL_COND_V_MSG(coff_header_machines.has(first_bytes), ERR_FILE_CORRUPT, vformat("Couldn't read OBJ file '%s', it seems to be binary, corrupted, or empty.", p_path));
f->seek(0);
Ref<ImporterMesh> mesh;
mesh.instantiate();
bool generate_tangents = p_generate_tangents;
Vector3 scale_mesh = p_scale_mesh;
Vector3 offset_mesh = p_offset_mesh;
Vector<Vector3> vertices;
Vector<Vector3> normals;
Vector<Vector2> uvs;
Vector<Color> colors;
const String default_name = "Mesh";
String name = default_name;
HashMap<String, HashMap<String, Ref<StandardMaterial3D>>> material_map;
Ref<SurfaceTool> surf_tool = memnew(SurfaceTool);
surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
String current_material_library;
String current_material;
String current_group;
uint32_t smooth_group = 0;
bool smoothing = true;
const uint32_t no_smoothing_smooth_group = (uint32_t)-1;
while (true) {
String l = f->get_line().strip_edges();
while (l.length() && l[l.length() - 1] == '\\') {
String add = f->get_line().strip_edges();
l += add;
if (add.is_empty()) {
break;
}
}
if (l.begins_with("v ")) {
//vertex
Vector<String> v = l.split(" ", false);
ERR_FAIL_COND_V(v.size() < 4, ERR_FILE_CORRUPT);
Vector3 vtx;
vtx.x = v[1].to_float() * scale_mesh.x + offset_mesh.x;
vtx.y = v[2].to_float() * scale_mesh.y + offset_mesh.y;
vtx.z = v[3].to_float() * scale_mesh.z + offset_mesh.z;
vertices.push_back(vtx);
//vertex color
if (v.size() >= 7) {
while (colors.size() < vertices.size() - 1) {
colors.push_back(Color(1.0, 1.0, 1.0));
}
Color c;
c.r = v[4].to_float();
c.g = v[5].to_float();
c.b = v[6].to_float();
colors.push_back(c);
} else if (!colors.is_empty()) {
colors.push_back(Color(1.0, 1.0, 1.0));
}
} else if (l.begins_with("vt ")) {
//uv
Vector<String> v = l.split(" ", false);
ERR_FAIL_COND_V(v.size() < 3, ERR_FILE_CORRUPT);
Vector2 uv;
uv.x = v[1].to_float();
uv.y = 1.0 - v[2].to_float();
uvs.push_back(uv);
} else if (l.begins_with("vn ")) {
//normal
Vector<String> v = l.split(" ", false);
ERR_FAIL_COND_V(v.size() < 4, ERR_FILE_CORRUPT);
Vector3 nrm;
nrm.x = v[1].to_float();
nrm.y = v[2].to_float();
nrm.z = v[3].to_float();
normals.push_back(nrm);
} else if (l.begins_with("f ")) {
//vertex
Vector<String> v = l.split(" ", false);
ERR_FAIL_COND_V(v.size() < 4, ERR_FILE_CORRUPT);
//not very fast, could be sped up
Vector<String> face[3];
face[0] = v[1].split("/");
face[1] = v[2].split("/");
ERR_FAIL_COND_V(face[0].is_empty(), ERR_FILE_CORRUPT);
ERR_FAIL_COND_V(face[0].size() != face[1].size(), ERR_FILE_CORRUPT);
for (int i = 2; i < v.size() - 1; i++) {
face[2] = v[i + 1].split("/");
ERR_FAIL_COND_V(face[0].size() != face[2].size(), ERR_FILE_CORRUPT);
for (int j = 0; j < 3; j++) {
int idx = j;
if (idx < 2) {
idx = 1 ^ idx;
}
if (face[idx].size() == 3) {
int norm = face[idx][2].to_int() - 1;
if (norm < 0) {
norm += normals.size() + 1;
}
ERR_FAIL_INDEX_V(norm, normals.size(), ERR_FILE_CORRUPT);
surf_tool->set_normal(normals[norm]);
if (generate_tangents && uvs.is_empty()) {
// We can't generate tangents without UVs, so create dummy tangents.
Vector3 tan = Vector3(normals[norm].z, -normals[norm].x, normals[norm].y).cross(normals[norm].normalized()).normalized();
surf_tool->set_tangent(Plane(tan.x, tan.y, tan.z, 1.0));
}
} else {
// No normals, use a dummy tangent since normals and tangents will be generated.
if (generate_tangents && uvs.is_empty()) {
// We can't generate tangents without UVs, so create dummy tangents.
surf_tool->set_tangent(Plane(1.0, 0.0, 0.0, 1.0));
}
}
if (face[idx].size() >= 2 && !face[idx][1].is_empty()) {
int uv = face[idx][1].to_int() - 1;
if (uv < 0) {
uv += uvs.size() + 1;
}
ERR_FAIL_INDEX_V(uv, uvs.size(), ERR_FILE_CORRUPT);
surf_tool->set_uv(uvs[uv]);
}
int vtx = face[idx][0].to_int() - 1;
if (vtx < 0) {
vtx += vertices.size() + 1;
}
ERR_FAIL_INDEX_V(vtx, vertices.size(), ERR_FILE_CORRUPT);
Vector3 vertex = vertices[vtx];
if (!colors.is_empty()) {
surf_tool->set_color(colors[vtx]);
}
surf_tool->set_smooth_group(smoothing ? smooth_group : no_smoothing_smooth_group);
surf_tool->add_vertex(vertex);
}
face[1] = face[2];
}
} else if (l.begins_with("s ")) { //smoothing
String what = l.substr(2, l.length()).strip_edges();
bool do_smooth;
if (what == "off") {
do_smooth = false;
} else {
do_smooth = true;
}
if (do_smooth != smoothing) {
smoothing = do_smooth;
if (smoothing) {
smooth_group++;
}
}
} else if (/*l.begins_with("g ") ||*/ l.begins_with("usemtl ") || (l.begins_with("o ") || f->eof_reached())) { //commit group to mesh
uint64_t mesh_flags = RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
if (p_disable_compression) {
mesh_flags = 0;
} else {
bool is_mesh_2d = true;
// Disable compression if all z equals 0 (the mesh is 2D).
for (int i = 0; i < vertices.size(); i++) {
if (!Math::is_zero_approx(vertices[i].z)) {
is_mesh_2d = false;
break;
}
}
if (is_mesh_2d) {
mesh_flags = 0;
}
}
//groups are too annoying
if (surf_tool->get_vertex_array().size()) {
//another group going on, commit it
if (normals.size() == 0) {
surf_tool->generate_normals();
}
if (generate_tangents && uvs.size()) {
surf_tool->generate_tangents();
}
surf_tool->index();
print_verbose("OBJ: Current material library " + current_material_library + " has " + itos(material_map.has(current_material_library)));
print_verbose("OBJ: Current material " + current_material + " has " + itos(material_map.has(current_material_library) && material_map[current_material_library].has(current_material)));
Ref<StandardMaterial3D> material;
if (material_map.has(current_material_library) && material_map[current_material_library].has(current_material)) {
material = material_map[current_material_library][current_material];
if (!colors.is_empty()) {
material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
}
surf_tool->set_material(material);
}
Array array = surf_tool->commit_to_arrays();
if (mesh_flags & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES && generate_tangents) {
// Compression is enabled, so let's validate that the normals and tangents are correct.
Vector<Vector3> norms = array[Mesh::ARRAY_NORMAL];
Vector<float> tangents = array[Mesh::ARRAY_TANGENT];
for (int vert = 0; vert < norms.size(); vert++) {
Vector3 tan = Vector3(tangents[vert * 4 + 0], tangents[vert * 4 + 1], tangents[vert * 4 + 2]);
if (abs(tan.dot(norms[vert])) > 0.0001) {
// Tangent is not perpendicular to the normal, so we can't use compression.
mesh_flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
}
}
}
mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, array, TypedArray<Array>(), Dictionary(), material, name, mesh_flags);
print_verbose("OBJ: Added surface :" + mesh->get_surface_name(mesh->get_surface_count() - 1));
if (!current_material.is_empty()) {
if (mesh->get_surface_count() >= 1) {
mesh->set_surface_name(mesh->get_surface_count() - 1, current_material.get_basename());
}
} else if (!current_group.is_empty()) {
if (mesh->get_surface_count() >= 1) {
mesh->set_surface_name(mesh->get_surface_count() - 1, current_group);
}
}
surf_tool->clear();
surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
}
if (l.begins_with("o ") || f->eof_reached()) {
if (!p_single_mesh) {
if (mesh->get_surface_count() > 0) {
mesh->set_name(name);
r_meshes.push_back(mesh);
mesh.instantiate();
}
name = default_name;
current_group = "";
current_material = "";
}
}
if (f->eof_reached()) {
break;
}
if (l.begins_with("o ")) {
name = l.substr(2, l.length()).strip_edges();
}
if (l.begins_with("usemtl ")) {
current_material = l.replace("usemtl", "").strip_edges();
}
if (l.begins_with("g ")) {
current_group = l.substr(2, l.length()).strip_edges();
}
} else if (l.begins_with("mtllib ")) { //parse material
current_material_library = l.replace("mtllib", "").strip_edges();
if (!material_map.has(current_material_library)) {
HashMap<String, Ref<StandardMaterial3D>> lib;
String lib_path = current_material_library;
if (lib_path.is_relative_path()) {
lib_path = p_path.get_base_dir().path_join(current_material_library);
}
Error err = _parse_material_library(lib_path, lib, r_missing_deps);
if (err == OK) {
material_map[current_material_library] = lib;
}
}
}
}
if (p_single_mesh && mesh->get_surface_count() > 0) {
r_meshes.push_back(mesh);
}
return OK;
}
Node *EditorOBJImporter::import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) {
List<Ref<ImporterMesh>> meshes;
Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, false, Vector3(1, 1, 1), Vector3(0, 0, 0), p_flags & IMPORT_FORCE_DISABLE_MESH_COMPRESSION, r_missing_deps);
if (err != OK) {
if (r_err) {
*r_err = err;
}
return nullptr;
}
Node3D *scene = memnew(Node3D);
for (Ref<ImporterMesh> m : meshes) {
ImporterMeshInstance3D *mi = memnew(ImporterMeshInstance3D);
mi->set_mesh(m);
mi->set_name(m->get_name());
scene->add_child(mi, true);
mi->set_owner(scene);
}
if (r_err) {
*r_err = OK;
}
return scene;
}
void EditorOBJImporter::get_extensions(List<String> *r_extensions) const {
r_extensions->push_back("obj");
}
EditorOBJImporter::EditorOBJImporter() {
}
////////////////////////////////////////////////////
String ResourceImporterOBJ::get_importer_name() const {
return "wavefront_obj";
}
String ResourceImporterOBJ::get_visible_name() const {
return "OBJ as Mesh";
}
void ResourceImporterOBJ::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("obj");
}
String ResourceImporterOBJ::get_save_extension() const {
return "mesh";
}
String ResourceImporterOBJ::get_resource_type() const {
return "Mesh";
}
int ResourceImporterOBJ::get_format_version() const {
return 1;
}
int ResourceImporterOBJ::get_preset_count() const {
return 0;
}
String ResourceImporterOBJ::get_preset_name(int p_idx) const {
return "";
}
void ResourceImporterOBJ::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_tangents"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "scale_mesh"), Vector3(1, 1, 1)));
r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "offset_mesh"), Vector3(0, 0, 0)));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "optimize_mesh"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_disable_mesh_compression"), false));
}
bool ResourceImporterOBJ::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
return true;
}
Error ResourceImporterOBJ::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
List<Ref<ImporterMesh>> meshes;
Error err = _parse_obj(p_source_file, meshes, true, p_options["generate_tangents"], p_options["optimize_mesh"], p_options["scale_mesh"], p_options["offset_mesh"], p_options["force_disable_mesh_compression"], nullptr);
ERR_FAIL_COND_V(err != OK, err);
ERR_FAIL_COND_V(meshes.size() != 1, ERR_BUG);
String save_path = p_save_path + ".mesh";
err = ResourceSaver::save(meshes.front()->get()->get_mesh(), save_path);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save Mesh to file '" + save_path + "'.");
r_gen_files->push_back(save_path);
return OK;
}
ResourceImporterOBJ::ResourceImporterOBJ() {
}

View file

@ -0,0 +1,72 @@
/**************************************************************************/
/* resource_importer_obj.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_OBJ_H
#define RESOURCE_IMPORTER_OBJ_H
#include "resource_importer_scene.h"
class EditorOBJImporter : public EditorSceneFormatImporter {
GDCLASS(EditorOBJImporter, EditorSceneFormatImporter);
public:
virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List<String> *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr) override;
EditorOBJImporter();
};
class ResourceImporterOBJ : public ResourceImporter {
GDCLASS(ResourceImporterOBJ, ResourceImporter);
public:
virtual String get_importer_name() const override;
virtual String get_visible_name() const override;
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
virtual String get_save_extension() const override;
virtual String get_resource_type() const override;
virtual int get_format_version() const override;
virtual int get_preset_count() const override;
virtual String get_preset_name(int p_idx) const override;
virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const override;
virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
// Threaded import can currently cause deadlocks, see GH-48265.
virtual bool can_import_threaded() const override { return false; }
ResourceImporterOBJ();
};
#endif // RESOURCE_IMPORTER_OBJ_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,525 @@
/**************************************************************************/
/* resource_importer_scene.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_SCENE_H
#define RESOURCE_IMPORTER_SCENE_H
#include "core/error/error_macros.h"
#include "core/io/resource_importer.h"
#include "core/variant/dictionary.h"
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/resources/3d/box_shape_3d.h"
#include "scene/resources/3d/capsule_shape_3d.h"
#include "scene/resources/3d/cylinder_shape_3d.h"
#include "scene/resources/3d/importer_mesh.h"
#include "scene/resources/3d/skin.h"
#include "scene/resources/3d/sphere_shape_3d.h"
#include "scene/resources/animation.h"
#include "scene/resources/mesh.h"
class Material;
class AnimationPlayer;
class ImporterMesh;
class EditorSceneFormatImporter : public RefCounted {
GDCLASS(EditorSceneFormatImporter, RefCounted);
protected:
static void _bind_methods();
Node *import_scene_wrapper(const String &p_path, uint32_t p_flags, const Dictionary &p_options);
Ref<Animation> import_animation_wrapper(const String &p_path, uint32_t p_flags, const Dictionary &p_options);
GDVIRTUAL0RC(uint32_t, _get_import_flags)
GDVIRTUAL0RC(Vector<String>, _get_extensions)
GDVIRTUAL3R(Object *, _import_scene, String, uint32_t, Dictionary)
GDVIRTUAL1(_get_import_options, String)
GDVIRTUAL3RC(Variant, _get_option_visibility, String, bool, String)
public:
enum ImportFlags {
IMPORT_SCENE = 1,
IMPORT_ANIMATION = 2,
IMPORT_FAIL_ON_MISSING_DEPENDENCIES = 4,
IMPORT_GENERATE_TANGENT_ARRAYS = 8,
IMPORT_USE_NAMED_SKIN_BINDS = 16,
IMPORT_DISCARD_MESHES_AND_MATERIALS = 32, //used for optimizing animation import
IMPORT_FORCE_DISABLE_MESH_COMPRESSION = 64,
};
virtual uint32_t get_import_flags() const;
virtual void get_extensions(List<String> *r_extensions) const;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr);
virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options);
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options);
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {}
EditorSceneFormatImporter() {}
};
class EditorScenePostImport : public RefCounted {
GDCLASS(EditorScenePostImport, RefCounted);
String source_file;
protected:
static void _bind_methods();
GDVIRTUAL1R(Object *, _post_import, Node *)
public:
String get_source_file() const;
virtual Node *post_import(Node *p_scene);
virtual void init(const String &p_source_file);
EditorScenePostImport();
};
class EditorScenePostImportPlugin : public RefCounted {
GDCLASS(EditorScenePostImportPlugin, RefCounted);
public:
enum InternalImportCategory {
INTERNAL_IMPORT_CATEGORY_NODE,
INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE,
INTERNAL_IMPORT_CATEGORY_MESH,
INTERNAL_IMPORT_CATEGORY_MATERIAL,
INTERNAL_IMPORT_CATEGORY_ANIMATION,
INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE,
INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE,
INTERNAL_IMPORT_CATEGORY_MAX
};
private:
mutable const HashMap<StringName, Variant> *current_options = nullptr;
mutable const Dictionary *current_options_dict = nullptr;
List<ResourceImporter::ImportOption> *current_option_list = nullptr;
InternalImportCategory current_category = INTERNAL_IMPORT_CATEGORY_MAX;
protected:
GDVIRTUAL1(_get_internal_import_options, int)
GDVIRTUAL3RC(Variant, _get_internal_option_visibility, int, bool, String)
GDVIRTUAL2RC(Variant, _get_internal_option_update_view_required, int, String)
GDVIRTUAL4(_internal_process, int, Node *, Node *, Ref<Resource>)
GDVIRTUAL1(_get_import_options, String)
GDVIRTUAL3RC(Variant, _get_option_visibility, String, bool, String)
GDVIRTUAL1(_pre_process, Node *)
GDVIRTUAL1(_post_process, Node *)
static void _bind_methods();
public:
Variant get_option_value(const StringName &p_name) const;
void add_import_option(const String &p_name, const Variant &p_default_value);
void add_import_option_advanced(Variant::Type p_type, const String &p_name, const Variant &p_default_value, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = String(), int p_usage_flags = PROPERTY_USAGE_DEFAULT);
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options);
virtual Variant get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
virtual Variant get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options);
virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options);
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
virtual void pre_process(Node *p_scene, const HashMap<StringName, Variant> &p_options);
virtual void post_process(Node *p_scene, const HashMap<StringName, Variant> &p_options);
EditorScenePostImportPlugin() {}
};
VARIANT_ENUM_CAST(EditorScenePostImportPlugin::InternalImportCategory)
class ResourceImporterScene : public ResourceImporter {
GDCLASS(ResourceImporterScene, ResourceImporter);
static Vector<Ref<EditorSceneFormatImporter>> scene_importers;
static Vector<Ref<EditorScenePostImportPlugin>> post_importer_plugins;
static ResourceImporterScene *scene_singleton;
static ResourceImporterScene *animation_singleton;
enum LightBakeMode {
LIGHT_BAKE_DISABLED,
LIGHT_BAKE_STATIC,
LIGHT_BAKE_STATIC_LIGHTMAPS,
LIGHT_BAKE_DYNAMIC,
};
enum MeshPhysicsMode {
MESH_PHYSICS_DISABLED,
MESH_PHYSICS_MESH_AND_STATIC_COLLIDER,
MESH_PHYSICS_RIGID_BODY_AND_MESH,
MESH_PHYSICS_STATIC_COLLIDER_ONLY,
MESH_PHYSICS_AREA_ONLY,
};
enum NavMeshMode {
NAVMESH_DISABLED,
NAVMESH_MESH_AND_NAVMESH,
NAVMESH_NAVMESH_ONLY,
};
enum OccluderMode {
OCCLUDER_DISABLED,
OCCLUDER_MESH_AND_OCCLUDER,
OCCLUDER_OCCLUDER_ONLY,
};
enum MeshOverride {
MESH_OVERRIDE_DEFAULT,
MESH_OVERRIDE_ENABLE,
MESH_OVERRIDE_DISABLE,
};
enum BodyType {
BODY_TYPE_STATIC,
BODY_TYPE_DYNAMIC,
BODY_TYPE_AREA
};
enum ShapeType {
SHAPE_TYPE_DECOMPOSE_CONVEX,
SHAPE_TYPE_SIMPLE_CONVEX,
SHAPE_TYPE_TRIMESH,
SHAPE_TYPE_BOX,
SHAPE_TYPE_SPHERE,
SHAPE_TYPE_CYLINDER,
SHAPE_TYPE_CAPSULE,
SHAPE_TYPE_AUTOMATIC,
};
static Error _check_resource_save_paths(const Dictionary &p_data);
Array _get_skinned_pose_transforms(ImporterMeshInstance3D *p_src_mesh_node);
void _replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner);
Node *_generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches);
void _add_shapes(Node *p_node, const Vector<Ref<Shape3D>> &p_shapes);
void _copy_meta(Object *p_src_object, Object *p_dst_object);
enum AnimationImportTracks {
ANIMATION_IMPORT_TRACKS_IF_PRESENT,
ANIMATION_IMPORT_TRACKS_IF_PRESENT_FOR_ALL,
ANIMATION_IMPORT_TRACKS_NEVER,
};
enum TrackChannel {
TRACK_CHANNEL_POSITION,
TRACK_CHANNEL_ROTATION,
TRACK_CHANNEL_SCALE,
TRACK_CHANNEL_BLEND_SHAPE,
TRACK_CHANNEL_MAX
};
void _optimize_track_usage(AnimationPlayer *p_player, AnimationImportTracks *p_track_actions);
bool animation_importer = false;
public:
static ResourceImporterScene *get_scene_singleton() { return scene_singleton; }
static ResourceImporterScene *get_animation_singleton() { return animation_singleton; }
static void add_post_importer_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin, bool p_first_priority = false);
static void remove_post_importer_plugin(const Ref<EditorScenePostImportPlugin> &p_plugin);
const Vector<Ref<EditorSceneFormatImporter>> &get_scene_importers() const { return scene_importers; }
static void add_scene_importer(Ref<EditorSceneFormatImporter> p_importer, bool p_first_priority = false);
static void remove_scene_importer(Ref<EditorSceneFormatImporter> p_importer);
static void get_scene_importer_extensions(List<String> *p_extensions);
static void clean_up_importer_plugins();
virtual String get_importer_name() const override;
virtual String get_visible_name() const override;
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
virtual String get_save_extension() const override;
virtual String get_resource_type() const override;
virtual int get_format_version() const override;
virtual int get_preset_count() const override;
virtual String get_preset_name(int p_idx) const override;
enum InternalImportCategory {
INTERNAL_IMPORT_CATEGORY_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_NODE,
INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE,
INTERNAL_IMPORT_CATEGORY_MESH = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MESH,
INTERNAL_IMPORT_CATEGORY_MATERIAL = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MATERIAL,
INTERNAL_IMPORT_CATEGORY_ANIMATION = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION,
INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE,
INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE,
INTERNAL_IMPORT_CATEGORY_MAX = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MAX
};
void get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const;
bool get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
bool get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const override;
virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
// Import scenes *after* everything else (such as textures).
virtual int get_import_order() const override { return ResourceImporter::IMPORT_ORDER_SCENE; }
void _pre_fix_global(Node *p_scene, const HashMap<StringName, Variant> &p_options) const;
Node *_pre_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &r_collision_map, Pair<PackedVector3Array, PackedInt32Array> *r_occluder_arrays, List<Pair<NodePath, Node *>> &r_node_renames);
Node *_pre_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps);
Node *_post_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Pair<PackedVector3Array, PackedInt32Array> &r_occluder_arrays, HashSet<Ref<ImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps, float p_applied_root_scale);
Node *_post_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps, bool p_remove_immutable_tracks);
Ref<Animation> _save_animation_to_file(Ref<Animation> anim, bool p_save_to_file, const String &p_save_to_path, bool p_keep_custom_tracks);
void _create_slices(AnimationPlayer *ap, Ref<Animation> anim, const Array &p_clips, bool p_bake_all);
void _optimize_animations(AnimationPlayer *anim, float p_max_vel_error, float p_max_ang_error, int p_prc_error);
void _compress_animations(AnimationPlayer *anim, int p_page_size_kb);
Node *pre_import(const String &p_source_file, const HashMap<StringName, Variant> &p_options);
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
virtual bool has_advanced_options() const override;
virtual void show_advanced_options(const String &p_path) override;
virtual bool can_import_threaded() const override { return false; }
ResourceImporterScene(bool p_animation_import = false, bool p_singleton = false);
~ResourceImporterScene();
template <typename M>
static Vector<Ref<Shape3D>> get_collision_shapes(const Ref<ImporterMesh> &p_mesh, const M &p_options, float p_applied_root_scale);
template <typename M>
static Transform3D get_collision_shapes_transform(const M &p_options);
};
class EditorSceneFormatImporterESCN : public EditorSceneFormatImporter {
GDCLASS(EditorSceneFormatImporterESCN, EditorSceneFormatImporter);
public:
virtual uint32_t get_import_flags() const override;
virtual void get_extensions(List<String> *r_extensions) const override;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr) override;
};
template <typename M>
Vector<Ref<Shape3D>> ResourceImporterScene::get_collision_shapes(const Ref<ImporterMesh> &p_mesh, const M &p_options, float p_applied_root_scale) {
ERR_FAIL_COND_V(p_mesh.is_null(), Vector<Ref<Shape3D>>());
ShapeType generate_shape_type = SHAPE_TYPE_AUTOMATIC;
if (p_options.has(SNAME("physics/shape_type"))) {
generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int();
}
if (generate_shape_type == SHAPE_TYPE_AUTOMATIC) {
BodyType body_type = BODY_TYPE_STATIC;
if (p_options.has(SNAME("physics/body_type"))) {
body_type = (BodyType)p_options[SNAME("physics/body_type")].operator int();
}
generate_shape_type = body_type == BODY_TYPE_DYNAMIC ? SHAPE_TYPE_DECOMPOSE_CONVEX : SHAPE_TYPE_TRIMESH;
}
if (generate_shape_type == SHAPE_TYPE_DECOMPOSE_CONVEX) {
Ref<MeshConvexDecompositionSettings> decomposition_settings = Ref<MeshConvexDecompositionSettings>();
decomposition_settings.instantiate();
bool advanced = false;
if (p_options.has(SNAME("decomposition/advanced"))) {
advanced = p_options[SNAME("decomposition/advanced")];
}
if (advanced) {
if (p_options.has(SNAME("decomposition/max_concavity"))) {
decomposition_settings->set_max_concavity(p_options[SNAME("decomposition/max_concavity")]);
}
if (p_options.has(SNAME("decomposition/symmetry_planes_clipping_bias"))) {
decomposition_settings->set_symmetry_planes_clipping_bias(p_options[SNAME("decomposition/symmetry_planes_clipping_bias")]);
}
if (p_options.has(SNAME("decomposition/revolution_axes_clipping_bias"))) {
decomposition_settings->set_revolution_axes_clipping_bias(p_options[SNAME("decomposition/revolution_axes_clipping_bias")]);
}
if (p_options.has(SNAME("decomposition/min_volume_per_convex_hull"))) {
decomposition_settings->set_min_volume_per_convex_hull(p_options[SNAME("decomposition/min_volume_per_convex_hull")]);
}
if (p_options.has(SNAME("decomposition/resolution"))) {
decomposition_settings->set_resolution(p_options[SNAME("decomposition/resolution")]);
}
if (p_options.has(SNAME("decomposition/max_num_vertices_per_convex_hull"))) {
decomposition_settings->set_max_num_vertices_per_convex_hull(p_options[SNAME("decomposition/max_num_vertices_per_convex_hull")]);
}
if (p_options.has(SNAME("decomposition/plane_downsampling"))) {
decomposition_settings->set_plane_downsampling(p_options[SNAME("decomposition/plane_downsampling")]);
}
if (p_options.has(SNAME("decomposition/convexhull_downsampling"))) {
decomposition_settings->set_convex_hull_downsampling(p_options[SNAME("decomposition/convexhull_downsampling")]);
}
if (p_options.has(SNAME("decomposition/normalize_mesh"))) {
decomposition_settings->set_normalize_mesh(p_options[SNAME("decomposition/normalize_mesh")]);
}
if (p_options.has(SNAME("decomposition/mode"))) {
decomposition_settings->set_mode((MeshConvexDecompositionSettings::Mode)p_options[SNAME("decomposition/mode")].operator int());
}
if (p_options.has(SNAME("decomposition/convexhull_approximation"))) {
decomposition_settings->set_convex_hull_approximation(p_options[SNAME("decomposition/convexhull_approximation")]);
}
if (p_options.has(SNAME("decomposition/max_convex_hulls"))) {
decomposition_settings->set_max_convex_hulls(MAX(1, (int)p_options[SNAME("decomposition/max_convex_hulls")]));
}
if (p_options.has(SNAME("decomposition/project_hull_vertices"))) {
decomposition_settings->set_project_hull_vertices(p_options[SNAME("decomposition/project_hull_vertices")]);
}
} else {
int precision_level = 5;
if (p_options.has(SNAME("decomposition/precision"))) {
precision_level = p_options[SNAME("decomposition/precision")];
}
const real_t precision = real_t(precision_level - 1) / 9.0;
decomposition_settings->set_max_concavity(Math::lerp(real_t(1.0), real_t(0.001), precision));
decomposition_settings->set_min_volume_per_convex_hull(Math::lerp(real_t(0.01), real_t(0.0001), precision));
decomposition_settings->set_resolution(Math::lerp(10'000, 100'000, precision));
decomposition_settings->set_max_num_vertices_per_convex_hull(Math::lerp(32, 64, precision));
decomposition_settings->set_plane_downsampling(Math::lerp(3, 16, precision));
decomposition_settings->set_convex_hull_downsampling(Math::lerp(3, 16, precision));
decomposition_settings->set_max_convex_hulls(Math::lerp(1, 32, precision));
}
return p_mesh->convex_decompose(decomposition_settings);
} else if (generate_shape_type == SHAPE_TYPE_SIMPLE_CONVEX) {
Vector<Ref<Shape3D>> shapes;
shapes.push_back(p_mesh->create_convex_shape(true, /*Passing false, otherwise VHACD will be used to simplify (Decompose) the Mesh.*/ false));
return shapes;
} else if (generate_shape_type == SHAPE_TYPE_TRIMESH) {
Vector<Ref<Shape3D>> shapes;
shapes.push_back(p_mesh->create_trimesh_shape());
return shapes;
} else if (generate_shape_type == SHAPE_TYPE_BOX) {
Ref<BoxShape3D> box;
box.instantiate();
if (p_options.has(SNAME("primitive/size"))) {
box->set_size(p_options[SNAME("primitive/size")].operator Vector3() * p_applied_root_scale);
} else {
box->set_size(Vector3(2, 2, 2) * p_applied_root_scale);
}
Vector<Ref<Shape3D>> shapes;
shapes.push_back(box);
return shapes;
} else if (generate_shape_type == SHAPE_TYPE_SPHERE) {
Ref<SphereShape3D> sphere;
sphere.instantiate();
if (p_options.has(SNAME("primitive/radius"))) {
sphere->set_radius(p_options[SNAME("primitive/radius")].operator float() * p_applied_root_scale);
} else {
sphere->set_radius(1.0f * p_applied_root_scale);
}
Vector<Ref<Shape3D>> shapes;
shapes.push_back(sphere);
return shapes;
} else if (generate_shape_type == SHAPE_TYPE_CYLINDER) {
Ref<CylinderShape3D> cylinder;
cylinder.instantiate();
if (p_options.has(SNAME("primitive/height"))) {
cylinder->set_height(p_options[SNAME("primitive/height")].operator float() * p_applied_root_scale);
} else {
cylinder->set_height(1.0f * p_applied_root_scale);
}
if (p_options.has(SNAME("primitive/radius"))) {
cylinder->set_radius(p_options[SNAME("primitive/radius")].operator float() * p_applied_root_scale);
} else {
cylinder->set_radius(1.0f * p_applied_root_scale);
}
Vector<Ref<Shape3D>> shapes;
shapes.push_back(cylinder);
return shapes;
} else if (generate_shape_type == SHAPE_TYPE_CAPSULE) {
Ref<CapsuleShape3D> capsule;
capsule.instantiate();
if (p_options.has(SNAME("primitive/height"))) {
capsule->set_height(p_options[SNAME("primitive/height")].operator float() * p_applied_root_scale);
} else {
capsule->set_height(1.0f * p_applied_root_scale);
}
if (p_options.has(SNAME("primitive/radius"))) {
capsule->set_radius(p_options[SNAME("primitive/radius")].operator float() * p_applied_root_scale);
} else {
capsule->set_radius(1.0f * p_applied_root_scale);
}
Vector<Ref<Shape3D>> shapes;
shapes.push_back(capsule);
return shapes;
}
return Vector<Ref<Shape3D>>();
}
template <typename M>
Transform3D ResourceImporterScene::get_collision_shapes_transform(const M &p_options) {
Transform3D transform;
ShapeType generate_shape_type = SHAPE_TYPE_AUTOMATIC;
if (p_options.has(SNAME("physics/shape_type"))) {
generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int();
}
if (generate_shape_type == SHAPE_TYPE_AUTOMATIC) {
BodyType body_type = BODY_TYPE_STATIC;
if (p_options.has(SNAME("physics/body_type"))) {
body_type = (BodyType)p_options[SNAME("physics/body_type")].operator int();
}
generate_shape_type = body_type == BODY_TYPE_DYNAMIC ? SHAPE_TYPE_DECOMPOSE_CONVEX : SHAPE_TYPE_TRIMESH;
}
if (generate_shape_type == SHAPE_TYPE_BOX ||
generate_shape_type == SHAPE_TYPE_SPHERE ||
generate_shape_type == SHAPE_TYPE_CYLINDER ||
generate_shape_type == SHAPE_TYPE_CAPSULE) {
if (p_options.has(SNAME("primitive/position"))) {
transform.origin = p_options[SNAME("primitive/position")];
}
if (p_options.has(SNAME("primitive/rotation"))) {
transform.basis = Basis::from_euler(p_options[SNAME("primitive/rotation")].operator Vector3() * (Math_PI / 180.0));
}
}
return transform;
}
#endif // RESOURCE_IMPORTER_SCENE_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,253 @@
/**************************************************************************/
/* scene_import_settings.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SCENE_IMPORT_SETTINGS_H
#define SCENE_IMPORT_SETTINGS_H
#include "editor/import/3d/resource_importer_scene.h"
#include "scene/3d/camera_3d.h"
#include "scene/3d/light_3d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/item_list.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/option_button.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/slider.h"
#include "scene/gui/split_container.h"
#include "scene/gui/subviewport_container.h"
#include "scene/gui/tab_container.h"
#include "scene/gui/tree.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "scene/resources/3d/sky_material.h"
class EditorFileDialog;
class EditorInspector;
class SceneImportSettingsData;
class SceneImportSettingsDialog : public ConfirmationDialog {
GDCLASS(SceneImportSettingsDialog, ConfirmationDialog)
static SceneImportSettingsDialog *singleton;
enum Actions {
ACTION_EXTRACT_MATERIALS,
ACTION_CHOOSE_MESH_SAVE_PATHS,
ACTION_CHOOSE_ANIMATION_SAVE_PATHS,
};
Node *scene = nullptr;
HSplitContainer *tree_split = nullptr;
HSplitContainer *property_split = nullptr;
TabContainer *data_mode = nullptr;
Tree *scene_tree = nullptr;
Tree *mesh_tree = nullptr;
Tree *material_tree = nullptr;
EditorInspector *inspector = nullptr;
SubViewport *base_viewport = nullptr;
Camera3D *camera = nullptr;
Ref<CameraAttributesPractical> camera_attributes;
Ref<Environment> environment;
Ref<Sky> sky;
Ref<ProceduralSkyMaterial> procedural_sky_material;
bool first_aabb = false;
AABB contents_aabb;
Button *light_1_switch = nullptr;
Button *light_2_switch = nullptr;
Button *light_rotate_switch = nullptr;
struct ThemeCache {
Ref<Texture2D> light_1_icon;
Ref<Texture2D> light_2_icon;
Ref<Texture2D> rotate_icon;
} theme_cache;
DirectionalLight3D *light1 = nullptr;
DirectionalLight3D *light2 = nullptr;
Ref<ArrayMesh> selection_mesh;
MeshInstance3D *node_selected = nullptr;
MeshInstance3D *mesh_preview = nullptr;
Ref<SphereMesh> material_preview;
AnimationPlayer *animation_player = nullptr;
List<Skeleton3D *> skeletons;
PanelContainer *animation_preview = nullptr;
HSlider *animation_slider = nullptr;
Button *animation_play_button = nullptr;
Button *animation_stop_button = nullptr;
Animation::LoopMode animation_loop_mode = Animation::LOOP_NONE;
bool animation_pingpong = false;
bool previous_import_as_skeleton = false;
bool previous_rest_as_reset = false;
Ref<StandardMaterial3D> collider_mat;
float cam_rot_x = 0.0f;
float cam_rot_y = 0.0f;
float cam_zoom = 0.0f;
void _update_scene();
struct MaterialData {
bool has_import_id;
Ref<Material> material;
TreeItem *scene_node = nullptr;
TreeItem *mesh_node = nullptr;
TreeItem *material_node = nullptr;
float cam_rot_x = -Math_PI / 4;
float cam_rot_y = -Math_PI / 4;
float cam_zoom = 1;
HashMap<StringName, Variant> settings;
};
HashMap<String, MaterialData> material_map;
HashMap<Ref<Material>, String> unnamed_material_name_map;
struct MeshData {
bool has_import_id;
Ref<Mesh> mesh;
TreeItem *scene_node = nullptr;
TreeItem *mesh_node = nullptr;
float cam_rot_x = -Math_PI / 4;
float cam_rot_y = -Math_PI / 4;
float cam_zoom = 1;
HashMap<StringName, Variant> settings;
};
HashMap<String, MeshData> mesh_map;
struct AnimationData {
Ref<Animation> animation;
TreeItem *scene_node = nullptr;
HashMap<StringName, Variant> settings;
};
HashMap<String, AnimationData> animation_map;
struct NodeData {
Node *node = nullptr;
TreeItem *scene_node = nullptr;
HashMap<StringName, Variant> settings;
};
HashMap<String, NodeData> node_map;
void _fill_material(Tree *p_tree, const Ref<Material> &p_material, TreeItem *p_parent);
void _fill_mesh(Tree *p_tree, const Ref<Mesh> &p_mesh, TreeItem *p_parent);
void _fill_animation(Tree *p_tree, const Ref<Animation> &p_anim, const String &p_name, TreeItem *p_parent);
void _fill_scene(Node *p_node, TreeItem *p_parent_item);
HashSet<Ref<Mesh>> mesh_set;
String selected_type;
String selected_id;
bool selecting = false;
void _update_view_gizmos();
void _update_camera();
void _select(Tree *p_from, const String &p_type, const String &p_id);
void _inspector_property_edited(const String &p_name);
void _reset_bone_transforms();
void _play_animation();
void _stop_current_animation();
void _reset_animation(const String &p_animation_name = "");
void _animation_slider_value_changed(double p_value);
void _animation_finished(const StringName &p_name);
void _material_tree_selected();
void _mesh_tree_selected();
void _scene_tree_selected();
void _cleanup();
void _on_light_1_switch_pressed();
void _on_light_2_switch_pressed();
void _on_light_rotate_switch_pressed();
void _viewport_input(const Ref<InputEvent> &p_input);
HashMap<StringName, Variant> defaults;
SceneImportSettingsData *scene_import_settings_data = nullptr;
void _re_import();
String base_path;
MenuButton *action_menu = nullptr;
ConfirmationDialog *external_paths = nullptr;
Tree *external_path_tree = nullptr;
EditorFileDialog *save_path = nullptr;
OptionButton *external_extension_type = nullptr;
EditorFileDialog *item_save_path = nullptr;
void _menu_callback(int p_id);
void _save_dir_callback(const String &p_path);
int current_action = 0;
Vector<TreeItem *> save_path_items;
TreeItem *save_path_item = nullptr;
void _save_path_changed(const String &p_path);
void _browse_save_callback(Object *p_item, int p_column, int p_id, MouseButton p_button);
void _save_dir_confirm();
Dictionary base_subresource_settings;
void _load_default_subresource_settings(HashMap<StringName, Variant> &settings, const String &p_type, const String &p_import_id, ResourceImporterScene::InternalImportCategory p_category);
bool editing_animation = false;
bool generate_collider = false;
Timer *update_view_timer = nullptr;
protected:
virtual void _update_theme_item_cache() override;
void _notification(int p_what);
public:
bool is_editing_animation() const { return editing_animation; }
void request_generate_collider();
void update_view();
void open_settings(const String &p_path, bool p_for_animation = false);
static SceneImportSettingsDialog *get_singleton();
Node *get_selected_node();
SceneImportSettingsDialog();
~SceneImportSettingsDialog();
};
#endif // SCENE_IMPORT_SETTINGS_H

View file

@ -0,0 +1,6 @@
#!/usr/bin/env python
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,660 @@
/**************************************************************************/
/* 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_stream_preview.h"
#include "editor/editor_file_system.h"
#include "editor/editor_string_names.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_icon(get_editor_theme_icon(SNAME("MainPlay")));
_stop_button->set_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_icon(get_editor_theme_icon(SNAME("ZoomMore")));
zoom_out->set_icon(get_editor_theme_icon(SNAME("ZoomLess")));
zoom_reset->set_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_valid()) {
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);
_preview->queue_redraw();
_indicator->queue_redraw();
}
void AudioStreamImportSettingsDialog::_preview_zoom_out() {
if (!stream.is_valid()) {
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);
_preview->queue_redraw();
_indicator->queue_redraw();
}
void AudioStreamImportSettingsDialog::_preview_zoom_reset() {
if (!stream.is_valid()) {
return;
}
zoom_bar->set_max(stream->get_length());
zoom_bar->set_page(zoom_bar->get_max());
zoom_bar->set_value(0);
_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_icon(get_editor_theme_icon(SNAME("MainPlay")));
set_process(false);
} else {
_player->play(_current);
_play_button->set_icon(get_editor_theme_icon(SNAME("Pause")));
set_process(true);
}
}
void AudioStreamImportSettingsDialog::_stop() {
_player->stop();
_play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay")));
_current = 0;
_indicator->queue_redraw();
set_process(false);
}
void AudioStreamImportSettingsDialog::_on_finished() {
_play_button->set_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_valid()) {
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_null()) {
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_null()) {
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));
List<String> keys;
config_file->get_section_keys("params", &keys);
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("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_max(10000);
loop_offset->set_step(0.001);
loop_offset->set_suffix("sec");
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("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_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("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->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->connect(SceneStringName(value_changed), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(bar_beats_edit);
interactive_hb->add_spacer();
main_vbox->add_margin_child(TTR("Music Playback:"), interactive_hb);
color_rect = memnew(ColorRect);
main_vbox->add_margin_child(TTR("Preview:"), color_rect);
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);
HBoxContainer *zoom_hbox = memnew(HBoxContainer);
zoom_bar = memnew(HScrollBar);
zoom_in = memnew(Button);
zoom_in->set_flat(true);
zoom_reset = memnew(Button);
zoom_reset->set_flat(true);
zoom_out = memnew(Button);
zoom_out->set_flat(true);
zoom_hbox->add_child(zoom_bar);
zoom_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
zoom_bar->set_v_size_flags(Control::SIZE_EXPAND_FILL);
zoom_hbox->add_child(zoom_out);
zoom_hbox->add_child(zoom_reset);
zoom_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));
zoom_bar->connect(SceneStringName(value_changed), callable_mp(this, &AudioStreamImportSettingsDialog::_preview_zoom_offset_changed));
vbox->add_child(zoom_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);
HBoxContainer *hbox = memnew(HBoxContainer);
hbox->add_theme_constant_override("separation", 0);
vbox->add_child(hbox);
_play_button = memnew(Button);
_play_button->set_flat(true);
hbox->add_child(_play_button);
_play_button->set_focus_mode(Control::FOCUS_NONE);
_play_button->connect(SceneStringName(pressed), callable_mp(this, &AudioStreamImportSettingsDialog::_play));
_stop_button = memnew(Button);
_stop_button->set_flat(true);
hbox->add_child(_stop_button);
_stop_button->set_focus_mode(Control::FOCUS_NONE);
_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);
singleton = this;
}

View file

@ -0,0 +1,117 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef AUDIO_STREAM_IMPORT_SETTINGS_H
#define AUDIO_STREAM_IMPORT_SETTINGS_H
#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"
#include "scene/resources/texture.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();
};
#endif // AUDIO_STREAM_IMPORT_SETTINGS_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,180 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef DYNAMIC_FONT_IMPORT_SETTINGS_H
#define DYNAMIC_FONT_IMPORT_SETTINGS_H
#include "editor/import/resource_importer_dynamic_font.h"
#include "core/templates/rb_set.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/item_list.h"
#include "scene/gui/option_button.h"
#include "scene/gui/split_container.h"
#include "scene/gui/subviewport_container.h"
#include "scene/gui/tab_container.h"
#include "scene/gui/text_edit.h"
#include "scene/gui/tree.h"
#include "scene/resources/font.h"
#include "servers/text_server.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;
// 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();
};
#endif // DYNAMIC_FONT_IMPORT_SETTINGS_H

View file

@ -0,0 +1,227 @@
/**************************************************************************/
/* 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/editor_file_system.h"
EditorImportPlugin::EditorImportPlugin() {
}
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(String(), "Unimplemented _get_preset_name in add-on.");
}
int EditorImportPlugin::get_preset_count() const {
int ret;
if (GDVIRTUAL_CALL(_get_preset_count, ret)) {
return ret;
}
ERR_FAIL_V_MSG(-1, "Unimplemented _get_preset_count in add-on.");
}
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;
}
ERR_FAIL_V_MSG(-1, "Unimplemented _get_priority in add-on.");
}
int EditorImportPlugin::get_import_order() const {
int ret;
if (GDVIRTUAL_CALL(_get_import_order, ret)) {
return ret;
}
ERR_FAIL_V_MSG(-1, "Unimplemented _get_import_order in add-on.");
}
void EditorImportPlugin::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options, int p_preset) const {
Array needed;
needed.push_back("name");
needed.push_back("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;
}
ERR_FAIL_V_MSG(false, "Unimplemented _get_option_visibility in add-on.");
}
Error EditorImportPlugin::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
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;
List<Variant> keys;
p_custom_options.get_key_list(&keys);
for (const Variant &K : keys) {
options.insert(K, p_custom_options[K]);
}
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) {
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_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,77 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef EDITOR_IMPORT_PLUGIN_H
#define EDITOR_IMPORT_PLUGIN_H
#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)
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:
EditorImportPlugin();
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 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(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());
};
#endif // EDITOR_IMPORT_PLUGIN_H

View file

@ -0,0 +1,113 @@
/**************************************************************************/
/* 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(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");
}
ResourceImporterBitMap::ResourceImporterBitMap() {
}
ResourceImporterBitMap::~ResourceImporterBitMap() {
}

View file

@ -0,0 +1,57 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_BITMASK_H
#define RESOURCE_IMPORTER_BITMASK_H
#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(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;
ResourceImporterBitMap();
~ResourceImporterBitMap();
};
#endif // RESOURCE_IMPORTER_BITMASK_H

View file

@ -0,0 +1,114 @@
/**************************************************************************/
/* 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(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 (List<String>::Element *E = image_files.front(); E; E = E->next()) {
Ref<ConfigFile> config;
config.instantiate();
err = config->load(E->get() + ".import");
if (err == OK) {
config->clear();
config->set_value("remap", "importer", "skip");
config->save(E->get() + ".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;
}
ResourceImporterBMFont::ResourceImporterBMFont() {
}

View file

@ -0,0 +1,56 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_BMFONT_H
#define RESOURCE_IMPORTER_BMFONT_H
#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(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;
ResourceImporterBMFont();
};
#endif // RESOURCE_IMPORTER_BMFONT_H

View file

@ -0,0 +1,156 @@
/**************************************************************************/
/* 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.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(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";
ResourceSaver::save(xlt, save_path);
if (r_gen_files) {
r_gen_files->push_back(save_path);
}
}
return OK;
}
ResourceImporterCSVTranslation::ResourceImporterCSVTranslation() {
}

View file

@ -0,0 +1,57 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_CSV_TRANSLATION_H
#define RESOURCE_IMPORTER_CSV_TRANSLATION_H
#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(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;
ResourceImporterCSVTranslation();
};
#endif // RESOURCE_IMPORTER_CSV_TRANSLATION_H

View file

@ -0,0 +1,238 @@
/**************************************************************************/
/* 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"
#include "modules/modules_enabled.gen.h" // For freetype.
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) {
#ifdef MODULE_FREETYPE_ENABLED
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");
#endif
}
}
String ResourceImporterDynamicFont::get_save_extension() const {
return "fontdata";
}
String ResourceImporterDynamicFont::get_resource_type() const {
return "FontFile";
}
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;
}
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::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"), 1));
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(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 allow_system_fallback = p_options["allow_system_fallback"];
int hinting = p_options["hinting"];
int subpixel_positioning = p_options["subpixel_positioning"];
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_allow_system_fallback(allow_system_fallback);
font->set_subpixel_positioning((TextServer::SubpixelPositioning)subpixel_positioning);
font->set_hinting((TextServer::Hinting)hinting);
font->set_oversampling(oversampling);
font->set_fallbacks(fallbacks);
Dictionary langs = p_options["language_support"];
for (int i = 0; i < langs.size(); i++) {
String key = langs.get_key_at_index(i);
bool enabled = langs.get_value_at_index(i);
font->set_language_support_override(key, enabled);
}
Dictionary scripts = p_options["script_support"];
for (int i = 0; i < scripts.size(); i++) {
String key = scripts.get_key_at_index(i);
bool enabled = scripts.get_value_at_index(i);
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;
}
ResourceImporterDynamicFont::ResourceImporterDynamicFont() {
}

View file

@ -0,0 +1,66 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_DYNAMIC_FONT_H
#define RESOURCE_IMPORTER_DYNAMIC_FONT_H
#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 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(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;
ResourceImporterDynamicFont();
};
#endif // RESOURCE_IMPORTER_DYNAMIC_FONT_H

View file

@ -0,0 +1,99 @@
/**************************************************************************/
/* 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"
#include "core/io/resource_saver.h"
#include "scene/resources/texture.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(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;
}
ResourceImporterImage::ResourceImporterImage() {
}

View file

@ -0,0 +1,58 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_IMAGE_H
#define RESOURCE_IMPORTER_IMAGE_H
#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(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;
ResourceImporterImage();
};
#endif // RESOURCE_IMPORTER_IMAGE_H

View file

@ -0,0 +1,346 @@
/**************************************************************************/
/* 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(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 count = 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_force_autohinter(false);
font->set_allow_system_fallback(false);
font->set_hinting(TextServer::HINTING_NONE);
font->set_oversampling(1.0f);
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) {
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') {
// 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);
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(" ", 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();
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();
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 (start == -1) {
WARN_PRINT(vformat("Invalid range: \"%s\"", range));
continue;
}
}
for (int32_t idx = MIN(start, end); idx <= MAX(start, end); idx++) {
ERR_FAIL_COND_V_MSG(pos >= count, ERR_CANT_CREATE, "Too many characters in range, should be " + itos(columns * rows));
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++;
}
}
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') {
char32_t charcode = kp_tokens[0].substr(i + 2, 4).hex_to_int();
from_tokens += charcode;
} 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') {
char32_t charcode = kp_tokens[1].substr(i + 2, 4).hex_to_int();
to_tokens += charcode;
} 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;
}
ResourceImporterImageFont::ResourceImporterImageFont() {
}

View file

@ -0,0 +1,56 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_IMAGEFONT_H
#define RESOURCE_IMPORTER_IMAGEFONT_H
#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(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;
ResourceImporterImageFont();
};
#endif // RESOURCE_IMPORTER_IMAGEFONT_H

View file

@ -0,0 +1,582 @@
/**************************************************************************/
/* 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/config_file.h"
#include "core/io/image_loader.h"
#include "core/object/ref_counted.h"
#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
#include "editor/import/resource_importer_texture.h"
#include "editor/import/resource_importer_texture_settings.h"
#include "scene/resources/compressed_texture.h"
#include "scene/resources/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;
}
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));
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, 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);
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);
}
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);
}
}
Error ResourceImporterLayeredTexture::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
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_BASIS_UNIVERSAL && image->get_format() >= Image::FORMAT_RF) {
//basis universal does not support float formats, fall back
compress_mode = COMPRESS_VRAM_COMPRESSED;
}
if (compress_mode == COMPRESS_VRAM_COMPRESSED) {
mipmaps = true;
//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->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);
}
}
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;
_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 {
//will become invalid if formats are missing to import
Dictionary meta = ResourceFormatImporter::get_singleton()->get_resource_metadata(p_path);
if (!meta.has("vram_texture")) {
return false;
}
bool vram = meta["vram_texture"];
if (!vram) {
return true; //do not care about non vram
}
Vector<String> formats_imported;
if (meta.has("imported_formats")) {
formats_imported = 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, 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 choses 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_NULL(r_texture_import->image);
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, 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, 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, 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,126 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_LAYERED_TEXTURE_H
#define RESOURCE_IMPORTER_LAYERED_TEXTURE_H
#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;
int hdr_compression = 0;
bool mipmaps = true;
bool high_quality = false;
Image::UsedChannels used_channels = Image::USED_CHANNELS_RGBA;
virtual ~LayeredTextureImport() {}
};
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, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2);
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
virtual bool are_import_settings_valid(const String &p_path) const override;
virtual String get_import_settings_string() const override;
void set_mode(Mode p_mode) { mode = p_mode; }
ResourceImporterLayeredTexture(bool p_singleton = false);
~ResourceImporterLayeredTexture();
};
#endif // RESOURCE_IMPORTER_LAYERED_TEXTURE_H

View file

@ -0,0 +1,120 @@
/**************************************************************************/
/* 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/marshalls.h"
#include "core/io/resource_saver.h"
#include "editor/editor_node.h"
#include "editor/plugins/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(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) {
/* STEP 1, Read shader code */
ERR_FAIL_COND_V_EDMSG((OS::get_singleton()->get_current_rendering_method() == "gl_compatibility"), ERR_UNAVAILABLE, "Cannot import custom .glsl shaders when using the gl_compatibility rendering_method. Please switch to the forward_plus or mobile rendering methods to use custom shaders.");
ERR_FAIL_COND_V_EDMSG((DisplayServer::get_singleton()->get_name() == "headless"), ERR_UNAVAILABLE, "Cannot import custom .glsl shaders when running in headless mode.");
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_valid(), 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;
}
ResourceImporterShaderFile::ResourceImporterShaderFile() {
}

View file

@ -0,0 +1,57 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_SHADER_FILE_H
#define RESOURCE_IMPORTER_SHADER_FILE_H
#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(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;
ResourceImporterShaderFile();
};
#endif // RESOURCE_IMPORTER_SHADER_FILE_H

View file

@ -0,0 +1,816 @@
/**************************************************************************/
/* resource_importer_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_texture.h"
#include "core/config/project_settings.h"
#include "core/io/config_file.h"
#include "core/io/image_loader.h"
#include "core/version.h"
#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
#include "editor/gui/editor_toaster.h"
#include "editor/import/resource_importer_texture_settings.h"
#include "editor/themes/editor_scale.h"
#include "editor/themes/editor_theme_manager.h"
#include "scene/resources/compressed_texture.h"
void ResourceImporterTexture::_texture_reimport_roughness(const Ref<CompressedTexture2D> &p_tex, const String &p_normal_path, RS::TextureDetectRoughnessChannel p_channel) {
ERR_FAIL_COND(p_tex.is_null());
MutexLock lock(singleton->mutex);
StringName path = p_tex->get_path();
if (!singleton->make_flags.has(path)) {
singleton->make_flags[path] = MakeInfo();
}
singleton->make_flags[path].flags |= MAKE_ROUGHNESS_FLAG;
singleton->make_flags[path].channel_for_roughness = p_channel;
singleton->make_flags[path].normal_path_for_roughness = p_normal_path;
}
void ResourceImporterTexture::_texture_reimport_3d(const Ref<CompressedTexture2D> &p_tex) {
ERR_FAIL_COND(p_tex.is_null());
MutexLock lock(singleton->mutex);
StringName path = p_tex->get_path();
if (!singleton->make_flags.has(path)) {
singleton->make_flags[path] = MakeInfo();
}
singleton->make_flags[path].flags |= MAKE_3D_FLAG;
}
void ResourceImporterTexture::_texture_reimport_normal(const Ref<CompressedTexture2D> &p_tex) {
ERR_FAIL_COND(p_tex.is_null());
MutexLock lock(singleton->mutex);
StringName path = p_tex->get_path();
if (!singleton->make_flags.has(path)) {
singleton->make_flags[path] = MakeInfo();
}
singleton->make_flags[path].flags |= MAKE_NORMAL_FLAG;
}
void ResourceImporterTexture::update_imports() {
if (EditorFileSystem::get_singleton()->is_scanning() || EditorFileSystem::get_singleton()->is_importing()) {
return; // do nothing for now
}
MutexLock lock(mutex);
Vector<String> to_reimport;
{
if (make_flags.is_empty()) {
return;
}
for (const KeyValue<StringName, MakeInfo> &E : make_flags) {
Ref<ConfigFile> cf;
cf.instantiate();
String src_path = String(E.key) + ".import";
Error err = cf->load(src_path);
ERR_CONTINUE(err != OK);
bool changed = false;
if (E.value.flags & MAKE_NORMAL_FLAG && int(cf->get_value("params", "compress/normal_map")) == 0) {
String message = vformat(TTR("%s: Texture detected as used as a normal map in 3D. Enabling red-green texture compression to reduce memory usage (blue channel is discarded)."), String(E.key));
#ifdef TOOLS_ENABLED
EditorToaster::get_singleton()->popup_str(message);
#endif
print_line(message);
cf->set_value("params", "compress/normal_map", 1);
changed = true;
}
if (E.value.flags & MAKE_ROUGHNESS_FLAG && int(cf->get_value("params", "roughness/mode")) == 0) {
String message = vformat(TTR("%s: Texture detected as used as a roughness map in 3D. Enabling roughness limiter based on the detected associated normal map at %s."), String(E.key), E.value.normal_path_for_roughness);
#ifdef TOOLS_ENABLED
EditorToaster::get_singleton()->popup_str(message);
#endif
print_line(message);
cf->set_value("params", "roughness/mode", E.value.channel_for_roughness + 2);
cf->set_value("params", "roughness/src_normal", E.value.normal_path_for_roughness);
changed = true;
}
if (E.value.flags & MAKE_3D_FLAG && bool(cf->get_value("params", "detect_3d/compress_to"))) {
const int compress_to = cf->get_value("params", "detect_3d/compress_to");
String compress_string;
cf->set_value("params", "detect_3d/compress_to", 0);
if (compress_to == 1) {
cf->set_value("params", "compress/mode", COMPRESS_VRAM_COMPRESSED);
compress_string = "VRAM Compressed (S3TC/ETC/BPTC)";
} else if (compress_to == 2) {
cf->set_value("params", "compress/mode", COMPRESS_BASIS_UNIVERSAL);
compress_string = "Basis Universal";
}
String message = vformat(TTR("%s: Texture detected as used in 3D. Enabling mipmap generation and setting the texture compression mode to %s."), String(E.key), compress_string);
#ifdef TOOLS_ENABLED
EditorToaster::get_singleton()->popup_str(message);
#endif
print_line(message);
cf->set_value("params", "mipmaps/generate", true);
changed = true;
}
if (changed) {
cf->save(src_path);
to_reimport.push_back(E.key);
}
}
make_flags.clear();
}
if (to_reimport.size()) {
EditorFileSystem::get_singleton()->reimport_files(to_reimport);
}
}
String ResourceImporterTexture::get_importer_name() const {
return "texture";
}
String ResourceImporterTexture::get_visible_name() const {
return "Texture2D";
}
void ResourceImporterTexture::get_recognized_extensions(List<String> *p_extensions) const {
ImageLoader::get_recognized_extensions(p_extensions);
}
String ResourceImporterTexture::get_save_extension() const {
return "ctex";
}
String ResourceImporterTexture::get_resource_type() const {
return "CompressedTexture2D";
}
bool ResourceImporterTexture::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
if (p_option == "compress/high_quality" || p_option == "compress/hdr_compression") {
int compress_mode = int(p_options["compress/mode"]);
if (compress_mode != COMPRESS_VRAM_COMPRESSED) {
return false;
}
} else if (p_option == "compress/lossy_quality") {
int compress_mode = int(p_options["compress/mode"]);
if (compress_mode != COMPRESS_LOSSY) {
return false;
}
} else if (p_option == "compress/hdr_mode") {
int compress_mode = int(p_options["compress/mode"]);
if (compress_mode < COMPRESS_VRAM_COMPRESSED) {
return false;
}
} else if (p_option == "compress/normal_map") {
int compress_mode = int(p_options["compress/mode"]);
if (compress_mode == COMPRESS_LOSSLESS) {
return false;
}
} else if (p_option == "mipmaps/limit") {
return p_options["mipmaps/generate"];
}
return true;
}
int ResourceImporterTexture::get_preset_count() const {
return 3;
}
String ResourceImporterTexture::get_preset_name(int p_idx) const {
static const char *preset_names[] = {
TTRC("2D/3D (Auto-Detect)"),
TTRC("2D"),
TTRC("3D"),
};
return TTRGET(preset_names[p_idx]);
}
void ResourceImporterTexture::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), p_preset == PRESET_3D ? 2 : 0));
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));
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/normal_map", PROPERTY_HINT_ENUM, "Detect,Enable,Disabled"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/channel_pack", PROPERTY_HINT_ENUM, "sRGB Friendly,Optimized"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "mipmaps/generate"), (p_preset == PRESET_3D ? true : false)));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "mipmaps/limit", PROPERTY_HINT_RANGE, "-1,256"), -1));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "roughness/mode", PROPERTY_HINT_ENUM, "Detect,Disabled,Red,Green,Blue,Alpha,Gray"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "roughness/src_normal", PROPERTY_HINT_FILE, "*.bmp,*.dds,*.exr,*.jpeg,*.jpg,*.hdr,*.png,*.svg,*.tga,*.webp"), ""));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/fix_alpha_border"), p_preset != PRESET_3D));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/premult_alpha"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/normal_map_invert_y"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/hdr_as_srgb"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/hdr_clamp_exposure"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "process/size_limit", PROPERTY_HINT_RANGE, "0,4096,1"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "detect_3d/compress_to", PROPERTY_HINT_ENUM, "Disabled,VRAM Compressed,Basis Universal"), (p_preset == PRESET_DETECT) ? 1 : 0));
// Do path based customization only if a path was passed.
if (p_path.is_empty() || p_path.get_extension() == "svg") {
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "svg/scale", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 1.0));
// Editor use only, applies to SVG.
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "editor/scale_with_editor_scale"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "editor/convert_colors_with_editor_theme"), false));
}
}
void ResourceImporterTexture::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) {
switch (p_compress_mode) {
case COMPRESS_LOSSLESS: {
bool lossless_force_png = GLOBAL_GET("rendering/textures/lossless_compression/force_png") ||
!Image::_webp_mem_loader_func; // WebP module disabled.
bool use_webp = !lossless_force_png && p_image->get_width() <= 16383 && p_image->get_height() <= 16383; // WebP has a size limit
f->store_32(use_webp ? CompressedTexture2D::DATA_FORMAT_WEBP : CompressedTexture2D::DATA_FORMAT_PNG);
f->store_16(p_image->get_width());
f->store_16(p_image->get_height());
f->store_32(p_image->get_mipmap_count());
f->store_32(p_image->get_format());
for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) {
Vector<uint8_t> data;
if (use_webp) {
data = Image::webp_lossless_packer(i ? p_image->get_image_from_mipmap(i) : p_image);
} else {
data = Image::png_packer(i ? p_image->get_image_from_mipmap(i) : p_image);
}
int data_len = data.size();
f->store_32(data_len);
const uint8_t *r = data.ptr();
f->store_buffer(r, data_len);
}
} break;
case COMPRESS_LOSSY: {
f->store_32(CompressedTexture2D::DATA_FORMAT_WEBP);
f->store_16(p_image->get_width());
f->store_16(p_image->get_height());
f->store_32(p_image->get_mipmap_count());
f->store_32(p_image->get_format());
for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) {
Vector<uint8_t> data = Image::webp_lossy_packer(i ? p_image->get_image_from_mipmap(i) : p_image, p_lossy_quality);
int data_len = data.size();
f->store_32(data_len);
const uint8_t *r = data.ptr();
f->store_buffer(r, data_len);
}
} break;
case COMPRESS_VRAM_COMPRESSED: {
Ref<Image> image = p_image->duplicate();
image->compress_from_channels(p_compress_format, p_channels);
f->store_32(CompressedTexture2D::DATA_FORMAT_IMAGE);
f->store_16(image->get_width());
f->store_16(image->get_height());
f->store_32(image->get_mipmap_count());
f->store_32(image->get_format());
Vector<uint8_t> data = image->get_data();
int dl = data.size();
const uint8_t *r = data.ptr();
f->store_buffer(r, dl);
} break;
case COMPRESS_VRAM_UNCOMPRESSED: {
f->store_32(CompressedTexture2D::DATA_FORMAT_IMAGE);
f->store_16(p_image->get_width());
f->store_16(p_image->get_height());
f->store_32(p_image->get_mipmap_count());
f->store_32(p_image->get_format());
Vector<uint8_t> data = p_image->get_data();
int dl = data.size();
const uint8_t *r = data.ptr();
f->store_buffer(r, dl);
} break;
case COMPRESS_BASIS_UNIVERSAL: {
f->store_32(CompressedTexture2D::DATA_FORMAT_BASIS_UNIVERSAL);
f->store_16(p_image->get_width());
f->store_16(p_image->get_height());
f->store_32(p_image->get_mipmap_count());
f->store_32(p_image->get_format());
Vector<uint8_t> data = Image::basis_universal_packer(p_image, p_channels);
int data_len = data.size();
f->store_32(data_len);
const uint8_t *r = data.ptr();
f->store_buffer(r, data_len);
} break;
}
}
void ResourceImporterTexture::_save_ctex(const Ref<Image> &p_image, const String &p_to_path, CompressMode p_compress_mode, float p_lossy_quality, Image::CompressMode p_vram_compression, bool p_mipmaps, bool p_streamable, bool p_detect_3d, bool p_detect_roughness, 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) {
Ref<FileAccess> f = FileAccess::open(p_to_path, FileAccess::WRITE);
ERR_FAIL_COND(f.is_null());
f->store_8('G');
f->store_8('S');
f->store_8('T');
f->store_8('2'); //godot streamable texture 2D
//format version
f->store_32(CompressedTexture2D::FORMAT_VERSION);
//texture may be resized later, so original size must be saved first
f->store_32(p_image->get_width());
f->store_32(p_image->get_height());
uint32_t flags = 0;
if (p_streamable) {
flags |= CompressedTexture2D::FORMAT_BIT_STREAM;
}
if (p_mipmaps) {
flags |= CompressedTexture2D::FORMAT_BIT_HAS_MIPMAPS; //mipmaps bit
}
if (p_detect_3d) {
flags |= CompressedTexture2D::FORMAT_BIT_DETECT_3D;
}
if (p_detect_roughness) {
flags |= CompressedTexture2D::FORMAT_BIT_DETECT_ROUGNESS;
}
if (p_detect_normal) {
flags |= CompressedTexture2D::FORMAT_BIT_DETECT_NORMAL;
}
f->store_32(flags);
f->store_32(p_limit_mipmap);
//reserved for future use
f->store_32(0);
f->store_32(0);
f->store_32(0);
if ((p_compress_mode == COMPRESS_LOSSLESS || p_compress_mode == COMPRESS_LOSSY) && p_image->get_format() > Image::FORMAT_RGBA8) {
p_compress_mode = COMPRESS_VRAM_UNCOMPRESSED; //these can't go as lossy
}
Ref<Image> image = p_image->duplicate();
if (p_force_po2_for_compressed && p_mipmaps && ((p_compress_mode == COMPRESS_BASIS_UNIVERSAL) || (p_compress_mode == COMPRESS_VRAM_COMPRESSED))) {
image->resize_to_po2();
}
if (p_mipmaps && (!image->has_mipmaps() || p_force_normal)) {
image->generate_mipmaps(p_force_normal);
}
if (!p_mipmaps) {
image->clear_mipmaps();
}
if (image->has_mipmaps() && p_normal.is_valid()) {
image->generate_mipmap_roughness(p_roughness_channel, p_normal);
}
Image::CompressSource csource = Image::COMPRESS_SOURCE_GENERIC;
if (p_force_normal) {
csource = Image::COMPRESS_SOURCE_NORMAL;
} else if (p_srgb_friendly) {
csource = Image::COMPRESS_SOURCE_SRGB;
}
Image::UsedChannels used_channels = image->detect_used_channels(csource);
save_to_ctex_format(f, image, p_compress_mode, used_channels, p_vram_compression, p_lossy_quality);
}
void ResourceImporterTexture::_save_editor_meta(const Dictionary &p_metadata, const String &p_to_path) {
Ref<FileAccess> f = FileAccess::open(p_to_path, FileAccess::WRITE);
ERR_FAIL_COND(f.is_null());
f->store_var(p_metadata);
}
Dictionary ResourceImporterTexture::_load_editor_meta(const String &p_path) const {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
ERR_FAIL_COND_V_MSG(f.is_null(), Dictionary(), vformat("Missing required editor-specific import metadata for a texture (please reimport it using the 'Import' tab): '%s'", p_path));
return f->get_var();
}
Error ResourceImporterTexture::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
// Parse import options.
int32_t loader_flags = ImageFormatLoader::FLAG_NONE;
// Compression.
CompressMode compress_mode = CompressMode(int(p_options["compress/mode"]));
const float lossy = p_options["compress/lossy_quality"];
const int pack_channels = p_options["compress/channel_pack"];
const int normal = p_options["compress/normal_map"];
const int hdr_compression = p_options["compress/hdr_compression"];
const int high_quality = p_options["compress/high_quality"];
// Mipmaps.
const bool mipmaps = p_options["mipmaps/generate"];
const uint32_t mipmap_limit = mipmaps ? uint32_t(p_options["mipmaps/limit"]) : uint32_t(-1);
// Roughness.
const int roughness = p_options["roughness/mode"];
const String normal_map = p_options["roughness/src_normal"];
// Processing.
const bool fix_alpha_border = p_options["process/fix_alpha_border"];
const bool premult_alpha = p_options["process/premult_alpha"];
const bool normal_map_invert_y = p_options["process/normal_map_invert_y"];
// Support for texture streaming is not implemented yet.
const bool stream = false;
const int size_limit = p_options["process/size_limit"];
const bool hdr_as_srgb = p_options["process/hdr_as_srgb"];
if (hdr_as_srgb) {
loader_flags |= ImageFormatLoader::FLAG_FORCE_LINEAR;
}
const bool hdr_clamp_exposure = p_options["process/hdr_clamp_exposure"];
float scale = 1.0;
// SVG-specific options.
if (p_options.has("svg/scale")) {
scale = p_options["svg/scale"];
}
// Editor-specific options.
bool use_editor_scale = p_options.has("editor/scale_with_editor_scale") && p_options["editor/scale_with_editor_scale"];
bool convert_editor_colors = p_options.has("editor/convert_colors_with_editor_theme") && p_options["editor/convert_colors_with_editor_theme"];
// Start importing images.
List<Ref<Image>> images_imported;
// Load the normal image.
Ref<Image> normal_image;
Image::RoughnessChannel roughness_channel = Image::ROUGHNESS_CHANNEL_R;
if (mipmaps && roughness > 1 && FileAccess::exists(normal_map)) {
normal_image.instantiate();
if (ImageLoader::load_image(normal_map, normal_image) == OK) {
roughness_channel = Image::RoughnessChannel(roughness - 2);
}
}
// Load the main image.
Ref<Image> image;
image.instantiate();
Error err = ImageLoader::load_image(p_source_file, image, nullptr, loader_flags, scale);
if (err != OK) {
return err;
}
images_imported.push_back(image);
// Load the editor-only image.
Ref<Image> editor_image;
bool import_editor_image = use_editor_scale || convert_editor_colors;
if (import_editor_image) {
float editor_scale = scale;
if (use_editor_scale) {
editor_scale = scale * EDSCALE;
}
int32_t editor_loader_flags = loader_flags;
if (convert_editor_colors) {
editor_loader_flags |= ImageFormatLoader::FLAG_CONVERT_COLORS;
}
editor_image.instantiate();
err = ImageLoader::load_image(p_source_file, editor_image, nullptr, editor_loader_flags, editor_scale);
if (err != OK) {
WARN_PRINT("Failed to import an image resource for editor use from '" + p_source_file + "'");
} else {
images_imported.push_back(editor_image);
}
}
for (Ref<Image> &target_image : images_imported) {
// Apply the size limit.
if (size_limit > 0 && (target_image->get_width() > size_limit || target_image->get_height() > size_limit)) {
if (target_image->get_width() >= target_image->get_height()) {
int new_width = size_limit;
int new_height = target_image->get_height() * new_width / target_image->get_width();
target_image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC);
} else {
int new_height = size_limit;
int new_width = target_image->get_width() * new_height / target_image->get_height();
target_image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC);
}
if (normal == 1) {
target_image->normalize();
}
}
// Fix alpha border.
if (fix_alpha_border) {
target_image->fix_alpha_edges();
}
// Premultiply the alpha.
if (premult_alpha) {
target_image->premultiply_alpha();
}
// Invert the green channel of the image to flip the normal map it contains.
if (normal_map_invert_y) {
// Inverting the green channel can be used to flip a normal map's direction.
// There's no standard when it comes to normal map Y direction, so this is
// sometimes needed when using a normal map exported from another program.
// See <http://wiki.polycount.com/wiki/Normal_Map_Technical_Details#Common_Swizzle_Coordinates>.
const int height = target_image->get_height();
const int width = target_image->get_width();
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
const Color color = target_image->get_pixel(i, j);
target_image->set_pixel(i, j, Color(color.r, 1 - color.g, color.b));
}
}
}
// Clamp HDR exposure.
if (hdr_clamp_exposure) {
// Clamp HDR exposure following Filament's tonemapping formula.
// This can be used to reduce fireflies in environment maps or reduce the influence
// of the sun from an HDRI panorama on environment lighting (when a DirectionalLight3D is used instead).
const int height = target_image->get_height();
const int width = target_image->get_width();
// These values are chosen arbitrarily and seem to produce good results with 4,096 samples.
const float linear = 4096.0;
const float compressed = 16384.0;
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
const Color color = target_image->get_pixel(i, j);
const float luma = color.get_luminance();
Color clamped_color;
if (luma <= linear) {
clamped_color = color;
} else {
clamped_color = (color / luma) * ((linear * linear - compressed * luma) / (2 * linear - compressed - luma));
}
target_image->set_pixel(i, j, clamped_color);
}
}
}
}
if (compress_mode == COMPRESS_BASIS_UNIVERSAL && image->get_format() >= Image::FORMAT_RF) {
// Basis universal does not support float formats, fallback.
compress_mode = COMPRESS_VRAM_COMPRESSED;
}
bool detect_3d = int(p_options["detect_3d/compress_to"]) > 0;
bool detect_roughness = roughness == 0;
bool detect_normal = normal == 0;
bool force_normal = normal == 1;
bool srgb_friendly_pack = pack_channels == 0;
Array formats_imported;
if (compress_mode == COMPRESS_VRAM_COMPRESSED) {
// Must import in all formats, in order of priority (so platform choses the best supported one. IE, etc2 over etc).
// Android, GLES 2.x
const bool is_hdr = (image->get_format() >= Image::FORMAT_RF && image->get_format() <= Image::FORMAT_RGBE9995);
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) {
formats_imported.push_back("s3tc_bptc");
}
if (can_etc2_astc) {
formats_imported.push_back("etc2_astc");
}
bool can_compress_hdr = hdr_compression > 0;
bool has_alpha = image->detect_alpha() != Image::ALPHA_NONE;
bool use_uncompressed = false;
if (is_hdr) {
if (has_alpha) {
// Can compress HDR, but HDR with alpha is not compressible.
if (hdr_compression == 2) {
// But user selected to compress HDR anyway, so force an alpha-less format.
if (image->get_format() == Image::FORMAT_RGBAF) {
image->convert(Image::FORMAT_RGBF);
} else if (image->get_format() == Image::FORMAT_RGBAH) {
image->convert(Image::FORMAT_RGBH);
}
} else {
can_compress_hdr = false;
}
}
if (!can_compress_hdr) {
// Fallback to RGBE99995.
if (image->get_format() != Image::FORMAT_RGBE9995) {
image->convert(Image::FORMAT_RGBE9995);
use_uncompressed = true;
}
}
}
if (use_uncompressed) {
_save_ctex(image, p_save_path + ".ctex", COMPRESS_VRAM_UNCOMPRESSED, lossy, Image::COMPRESS_S3TC /*this is ignored */, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
} else {
if (can_s3tc_bptc) {
Image::CompressMode image_compress_mode;
String image_compress_format;
if (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_ctex(image, p_save_path + "." + image_compress_format + ".ctex", compress_mode, lossy, image_compress_mode, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
r_platform_variants->push_back(image_compress_format);
}
if (can_etc2_astc) {
Image::CompressMode image_compress_mode;
String image_compress_format;
if (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_ctex(image, p_save_path + "." + image_compress_format + ".ctex", compress_mode, lossy, image_compress_mode, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
r_platform_variants->push_back(image_compress_format);
}
}
} else {
// Import normally.
_save_ctex(image, p_save_path + ".ctex", compress_mode, lossy, Image::COMPRESS_S3TC /*this is ignored */, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
}
if (editor_image.is_valid()) {
_save_ctex(editor_image, p_save_path + ".editor.ctex", compress_mode, lossy, Image::COMPRESS_S3TC /*this is ignored */, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
// Generate and save editor-specific metadata, which we cannot save to the .import file.
Dictionary editor_meta;
if (use_editor_scale) {
editor_meta["editor_scale"] = EDSCALE;
}
if (convert_editor_colors) {
editor_meta["editor_dark_theme"] = EditorThemeManager::is_dark_theme();
}
_save_editor_meta(editor_meta, p_save_path + ".editor.meta");
}
if (r_metadata) {
Dictionary meta;
meta["vram_texture"] = compress_mode == COMPRESS_VRAM_COMPRESSED;
if (formats_imported.size()) {
meta["imported_formats"] = formats_imported;
}
if (editor_image.is_valid()) {
meta["has_editor_variant"] = true;
}
*r_metadata = meta;
}
return OK;
}
const char *ResourceImporterTexture::compression_formats[] = {
"s3tc_bptc",
"etc2_astc",
nullptr
};
String ResourceImporterTexture::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 ResourceImporterTexture::are_import_settings_valid(const String &p_path) const {
Dictionary meta = ResourceFormatImporter::get_singleton()->get_resource_metadata(p_path);
if (meta.has("has_editor_variant")) {
String imported_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(p_path);
if (!FileAccess::exists(imported_path)) {
return false;
}
String editor_meta_path = imported_path.replace(".editor.ctex", ".editor.meta");
Dictionary editor_meta = _load_editor_meta(editor_meta_path);
if (editor_meta.has("editor_scale") && (float)editor_meta["editor_scale"] != EDSCALE) {
return false;
}
if (editor_meta.has("editor_dark_theme") && (bool)editor_meta["editor_dark_theme"] != EditorThemeManager::is_dark_theme()) {
return false;
}
}
if (!meta.has("vram_texture")) {
return false;
}
bool vram = meta["vram_texture"];
if (!vram) {
return true; // Do not care about non-VRAM.
}
// Will become invalid if formats are missing to import.
Vector<String> formats_imported;
if (meta.has("imported_formats")) {
formats_imported = 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;
}
ResourceImporterTexture *ResourceImporterTexture::singleton = nullptr;
ResourceImporterTexture::ResourceImporterTexture(bool p_singleton) {
// This should only be set through the EditorNode.
if (p_singleton) {
singleton = this;
}
CompressedTexture2D::request_3d_callback = _texture_reimport_3d;
CompressedTexture2D::request_roughness_callback = _texture_reimport_roughness;
CompressedTexture2D::request_normal_callback = _texture_reimport_normal;
}
ResourceImporterTexture::~ResourceImporterTexture() {
if (singleton == this) {
singleton = nullptr;
}
}

View file

@ -0,0 +1,114 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_TEXTURE_H
#define RESOURCE_IMPORTER_TEXTURE_H
#include "core/io/file_access.h"
#include "core/io/image.h"
#include "core/io/resource_importer.h"
#include "scene/resources/texture.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
};
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, 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;
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);
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(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;
void update_imports();
virtual bool are_import_settings_valid(const String &p_path) const override;
virtual String get_import_settings_string() const override;
ResourceImporterTexture(bool p_singleton = false);
~ResourceImporterTexture();
};
#endif // RESOURCE_IMPORTER_TEXTURE_H

View file

@ -0,0 +1,409 @@
/**************************************************************************/
/* 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/file_access.h"
#include "core/io/image_loader.h"
#include "core/io/resource_saver.h"
#include "core/math/geometry_2d.h"
#include "editor/editor_atlas_packer.h"
#include "scene/resources/atlas_texture.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(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_valid()) {
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;
}
ResourceImporterTextureAtlas::ResourceImporterTextureAtlas() {
}

View file

@ -0,0 +1,73 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_TEXTURE_ATLAS_H
#define RESOURCE_IMPORTER_TEXTURE_ATLAS_H
#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(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;
ResourceImporterTextureAtlas();
};
#endif // RESOURCE_IMPORTER_TEXTURE_ATLAS_H

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,41 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_TEXTURE_SETTINGS_H
#define RESOURCE_IMPORTER_TEXTURE_SETTINGS_H
// 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
#endif // RESOURCE_IMPORTER_TEXTURE_SETTINGS_H

View file

@ -0,0 +1,550 @@
/**************************************************************************/
/* 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/file_access.h"
#include "core/io/marshalls.h"
#include "core/io/resource_saver.h"
#include "scene/resources/audio_stream_wav.h"
const float TRIM_DB_LIMIT = -50;
const int TRIM_FADE_OUT_FRAMES = 500;
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));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Disabled,RAM (Ima-ADPCM),QOA (Quite OK Audio)"), 0));
}
Error ResourceImporterWAV::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
/* STEP 1, READ WAVE FILE */
Error err;
Ref<FileAccess> file = FileAccess::open(p_source_file, FileAccess::READ, &err);
ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_OPEN, "Cannot open file '" + p_source_file + "'.");
/* CHECK RIFF */
char riff[5];
riff[4] = 0;
file->get_buffer((uint8_t *)&riff, 4); //RIFF
if (riff[0] != 'R' || riff[1] != 'I' || riff[2] != 'F' || riff[3] != 'F') {
ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, vformat("Not a WAV file. File should start with 'RIFF', but found '%s', in file of size %d bytes", riff, file->get_length()));
}
/* GET FILESIZE */
file->get_32(); // filesize
/* CHECK WAVE */
char wave[5];
wave[4] = 0;
file->get_buffer((uint8_t *)&wave, 4); //WAVE
if (wave[0] != 'W' || wave[1] != 'A' || wave[2] != 'V' || wave[3] != 'E') {
ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, vformat("Not a WAV file. Header should contain 'WAVE', but found '%s', in file of size %d bytes", wave, file->get_length()));
}
// Let users override potential loop points from the WAV.
// We parse the WAV loop points only with "Detect From WAV" (0).
int import_loop_mode = p_options["edit/loop_mode"];
int format_bits = 0;
int format_channels = 0;
AudioStreamWAV::LoopMode loop_mode = AudioStreamWAV::LOOP_DISABLED;
uint16_t compression_code = 1;
bool format_found = false;
bool data_found = false;
int format_freq = 0;
int loop_begin = 0;
int loop_end = 0;
int frames = 0;
Vector<float> data;
while (!file->eof_reached()) {
/* chunk */
char chunkID[4];
file->get_buffer((uint8_t *)&chunkID, 4); //RIFF
/* chunk size */
uint32_t chunksize = file->get_32();
uint32_t file_pos = file->get_position(); //save file pos, so we can skip to next chunk safely
if (file->eof_reached()) {
//ERR_PRINT("EOF REACH");
break;
}
if (chunkID[0] == 'f' && chunkID[1] == 'm' && chunkID[2] == 't' && chunkID[3] == ' ' && !format_found) {
/* IS FORMAT CHUNK */
//Issue: #7755 : Not a bug - usage of other formats (format codes) are unsupported in current importer version.
//Consider revision for engine version 3.0
compression_code = file->get_16();
if (compression_code != 1 && compression_code != 3) {
ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Format not supported for WAVE file (not PCM). Save WAVE files as uncompressed PCM or IEEE float instead.");
}
format_channels = file->get_16();
if (format_channels != 1 && format_channels != 2) {
ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Format not supported for WAVE file (not stereo or mono).");
}
format_freq = file->get_32(); //sampling rate
file->get_32(); // average bits/second (unused)
file->get_16(); // block align (unused)
format_bits = file->get_16(); // bits per sample
if (format_bits % 8 || format_bits == 0) {
ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Invalid amount of bits in the sample (should be one of 8, 16, 24 or 32).");
}
if (compression_code == 3 && format_bits % 32) {
ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Invalid amount of bits in the IEEE float sample (should be 32 or 64).");
}
/* Don't need anything else, continue */
format_found = true;
}
if (chunkID[0] == 'd' && chunkID[1] == 'a' && chunkID[2] == 't' && chunkID[3] == 'a' && !data_found) {
/* IS DATA CHUNK */
data_found = true;
if (!format_found) {
ERR_PRINT("'data' chunk before 'format' chunk found.");
break;
}
frames = chunksize;
if (format_channels == 0) {
ERR_FAIL_COND_V(format_channels == 0, ERR_INVALID_DATA);
}
frames /= format_channels;
frames /= (format_bits >> 3);
/*print_line("chunksize: "+itos(chunksize));
print_line("channels: "+itos(format_channels));
print_line("bits: "+itos(format_bits));
*/
data.resize(frames * format_channels);
if (compression_code == 1) {
if (format_bits == 8) {
for (int i = 0; i < frames * format_channels; i++) {
// 8 bit samples are UNSIGNED
data.write[i] = int8_t(file->get_8() - 128) / 128.f;
}
} else if (format_bits == 16) {
for (int i = 0; i < frames * format_channels; i++) {
//16 bit SIGNED
data.write[i] = int16_t(file->get_16()) / 32768.f;
}
} else {
for (int i = 0; i < frames * format_channels; i++) {
//16+ bits samples are SIGNED
// if sample is > 16 bits, just read extra bytes
uint32_t s = 0;
for (int b = 0; b < (format_bits >> 3); b++) {
s |= ((uint32_t)file->get_8()) << (b * 8);
}
s <<= (32 - format_bits);
data.write[i] = (int32_t(s) >> 16) / 32768.f;
}
}
} else if (compression_code == 3) {
if (format_bits == 32) {
for (int i = 0; i < frames * format_channels; i++) {
//32 bit IEEE Float
data.write[i] = file->get_float();
}
} else if (format_bits == 64) {
for (int i = 0; i < frames * format_channels; i++) {
//64 bit IEEE Float
data.write[i] = file->get_double();
}
}
}
if (file->eof_reached()) {
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Premature end of file.");
}
}
if (import_loop_mode == 0 && chunkID[0] == 's' && chunkID[1] == 'm' && chunkID[2] == 'p' && chunkID[3] == 'l') {
// Loop point info!
/**
* Consider exploring next document:
* http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/RIFFNEW.pdf
* Especially on page:
* 16 - 17
* Timestamp:
* 22:38 06.07.2017 GMT
**/
for (int i = 0; i < 10; i++) {
file->get_32(); // i wish to know why should i do this... no doc!
}
// only read 0x00 (loop forward), 0x01 (loop ping-pong) and 0x02 (loop backward)
// Skip anything else because it's not supported, reserved for future uses or sampler specific
// from https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl (loop type values table)
int loop_type = file->get_32();
if (loop_type == 0x00 || loop_type == 0x01 || loop_type == 0x02) {
if (loop_type == 0x00) {
loop_mode = AudioStreamWAV::LOOP_FORWARD;
} else if (loop_type == 0x01) {
loop_mode = AudioStreamWAV::LOOP_PINGPONG;
} else if (loop_type == 0x02) {
loop_mode = AudioStreamWAV::LOOP_BACKWARD;
}
loop_begin = file->get_32();
loop_end = file->get_32();
}
}
// Move to the start of the next chunk. Note that RIFF requires a padding byte for odd
// chunk sizes.
file->seek(file_pos + chunksize + (chunksize & 1));
}
// STEP 2, APPLY CONVERSIONS
bool is16 = format_bits != 8;
int rate = format_freq;
/*
print_line("Input Sample: ");
print_line("\tframes: " + itos(frames));
print_line("\tformat_channels: " + itos(format_channels));
print_line("\t16bits: " + itos(is16));
print_line("\trate: " + itos(rate));
print_line("\tloop: " + itos(loop));
print_line("\tloop begin: " + itos(loop_begin));
print_line("\tloop end: " + itos(loop_end));
*/
//apply frequency limit
bool limit_rate = p_options["force/max_rate"];
int limit_rate_hz = p_options["force/max_rate_hz"];
if (limit_rate && rate > limit_rate_hz && rate > 0 && frames > 0) {
// resample!
int new_data_frames = (int)(frames * (float)limit_rate_hz / (float)rate);
Vector<float> new_data;
new_data.resize(new_data_frames * format_channels);
for (int c = 0; c < format_channels; c++) {
float frac = .0f;
int ipos = 0;
for (int i = 0; i < new_data_frames; i++) {
// Cubic interpolation should be enough.
float y0 = data[MAX(0, ipos - 1) * format_channels + c];
float y1 = data[ipos * format_channels + c];
float y2 = data[MIN(frames - 1, ipos + 1) * format_channels + c];
float y3 = data[MIN(frames - 1, ipos + 2) * format_channels + c];
new_data.write[i * format_channels + c] = Math::cubic_interpolate(y1, y2, y0, y3, frac);
// update position and always keep fractional part within ]0...1]
// in order to avoid 32bit floating point precision errors
frac += (float)rate / (float)limit_rate_hz;
int tpos = (int)Math::floor(frac);
ipos += tpos;
frac -= tpos;
}
}
if (loop_mode) {
loop_begin = (int)(loop_begin * (float)new_data_frames / (float)frames);
loop_end = (int)(loop_end * (float)new_data_frames / (float)frames);
}
data = new_data;
rate = limit_rate_hz;
frames = new_data_frames;
}
bool normalize = p_options["edit/normalize"];
if (normalize) {
float max = 0;
for (int i = 0; i < data.size(); i++) {
float amp = Math::abs(data[i]);
if (amp > max) {
max = amp;
}
}
if (max > 0) {
float mult = 1.0 / max;
for (int i = 0; i < data.size(); i++) {
data.write[i] *= mult;
}
}
}
bool trim = p_options["edit/trim"];
if (trim && (loop_mode == AudioStreamWAV::LOOP_DISABLED) && format_channels > 0) {
int first = 0;
int last = (frames / format_channels) - 1;
bool found = false;
float limit = Math::db_to_linear(TRIM_DB_LIMIT);
for (int i = 0; i < data.size() / format_channels; i++) {
float ampChannelSum = 0;
for (int j = 0; j < format_channels; j++) {
ampChannelSum += Math::abs(data[(i * format_channels) + j]);
}
float amp = Math::abs(ampChannelSum / (float)format_channels);
if (!found && amp > limit) {
first = i;
found = true;
}
if (found && amp > limit) {
last = i;
}
}
if (first < last) {
Vector<float> new_data;
new_data.resize((last - first) * format_channels);
for (int i = first; i < last; i++) {
float fadeOutMult = 1;
if (last - i < TRIM_FADE_OUT_FRAMES) {
fadeOutMult = ((float)(last - i - 1) / (float)TRIM_FADE_OUT_FRAMES);
}
for (int j = 0; j < format_channels; j++) {
new_data.write[((i - first) * format_channels) + j] = data[(i * format_channels) + j] * fadeOutMult;
}
}
data = new_data;
frames = data.size() / format_channels;
}
}
if (import_loop_mode >= 2) {
loop_mode = (AudioStreamWAV::LoopMode)(import_loop_mode - 1);
loop_begin = p_options["edit/loop_begin"];
loop_end = p_options["edit/loop_end"];
// Wrap around to max frames, so `-1` can be used to select the end, etc.
if (loop_begin < 0) {
loop_begin = CLAMP(loop_begin + frames + 1, 0, frames);
}
if (loop_end < 0) {
loop_end = CLAMP(loop_end + frames + 1, 0, frames);
}
}
int compression = p_options["compress/mode"];
bool force_mono = p_options["force/mono"];
if (force_mono && format_channels == 2) {
Vector<float> new_data;
new_data.resize(data.size() / 2);
for (int i = 0; i < frames; i++) {
new_data.write[i] = (data[i * 2 + 0] + data[i * 2 + 1]) / 2.0;
}
data = new_data;
format_channels = 1;
}
bool force_8_bit = p_options["force/8_bit"];
if (force_8_bit) {
is16 = false;
}
Vector<uint8_t> pcm_data;
AudioStreamWAV::Format dst_format;
if (compression == 1) {
dst_format = AudioStreamWAV::FORMAT_IMA_ADPCM;
if (format_channels == 1) {
_compress_ima_adpcm(data, pcm_data);
} else {
//byte interleave
Vector<float> left;
Vector<float> right;
int tframes = data.size() / 2;
left.resize(tframes);
right.resize(tframes);
for (int i = 0; i < tframes; i++) {
left.write[i] = data[i * 2 + 0];
right.write[i] = data[i * 2 + 1];
}
Vector<uint8_t> bleft;
Vector<uint8_t> bright;
_compress_ima_adpcm(left, bleft);
_compress_ima_adpcm(right, bright);
int dl = bleft.size();
pcm_data.resize(dl * 2);
uint8_t *w = pcm_data.ptrw();
const uint8_t *rl = bleft.ptr();
const uint8_t *rr = bright.ptr();
for (int i = 0; i < dl; i++) {
w[i * 2 + 0] = rl[i];
w[i * 2 + 1] = rr[i];
}
}
} else {
dst_format = is16 ? AudioStreamWAV::FORMAT_16_BITS : AudioStreamWAV::FORMAT_8_BITS;
bool enforce16 = is16 || compression == 2;
pcm_data.resize(data.size() * (enforce16 ? 2 : 1));
{
uint8_t *w = pcm_data.ptrw();
int ds = data.size();
for (int i = 0; i < ds; i++) {
if (enforce16) {
int16_t v = CLAMP(data[i] * 32768, -32768, 32767);
encode_uint16(v, &w[i * 2]);
} else {
int8_t v = CLAMP(data[i] * 128, -128, 127);
w[i] = v;
}
}
}
}
Vector<uint8_t> dst_data;
if (compression == 2) {
dst_format = AudioStreamWAV::FORMAT_QOA;
qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } };
uint32_t qoa_len = 0;
desc.samplerate = rate;
desc.samples = frames;
desc.channels = format_channels;
void *encoded = qoa_encode((short *)pcm_data.ptrw(), &desc, &qoa_len);
dst_data.resize(qoa_len);
memcpy(dst_data.ptrw(), encoded, qoa_len);
} else {
dst_data = pcm_data;
}
Ref<AudioStreamWAV> sample;
sample.instantiate();
sample->set_data(dst_data);
sample->set_format(dst_format);
sample->set_mix_rate(rate);
sample->set_loop_mode(loop_mode);
sample->set_loop_begin(loop_begin);
sample->set_loop_end(loop_end);
sample->set_stereo(format_channels == 2);
ResourceSaver::save(sample, p_save_path + ".sample");
return OK;
}
ResourceImporterWAV::ResourceImporterWAV() {
}

View file

@ -0,0 +1,148 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef RESOURCE_IMPORTER_WAV_H
#define RESOURCE_IMPORTER_WAV_H
#include "core/io/resource_importer.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;
static void _compress_ima_adpcm(const Vector<float> &p_data, Vector<uint8_t> &dst_data) {
static const int16_t _ima_adpcm_step_table[89] = {
7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};
static const int8_t _ima_adpcm_index_table[16] = {
-1, -1, -1, -1, 2, 4, 6, 8,
-1, -1, -1, -1, 2, 4, 6, 8
};
int datalen = p_data.size();
int datamax = datalen;
if (datalen & 1) {
datalen++;
}
dst_data.resize(datalen / 2 + 4);
uint8_t *w = dst_data.ptrw();
int i, step_idx = 0, prev = 0;
uint8_t *out = w;
const float *in = p_data.ptr();
// Initial value is zero.
*(out++) = 0;
*(out++) = 0;
// Table index initial value.
*(out++) = 0;
// Unused.
*(out++) = 0;
for (i = 0; i < datalen; i++) {
int step, diff, vpdiff, mask;
uint8_t nibble;
int16_t xm_sample;
if (i >= datamax) {
xm_sample = 0;
} else {
xm_sample = CLAMP(in[i] * 32767.0, -32768, 32767);
}
diff = (int)xm_sample - prev;
nibble = 0;
step = _ima_adpcm_step_table[step_idx];
vpdiff = step >> 3;
if (diff < 0) {
nibble = 8;
diff = -diff;
}
mask = 4;
while (mask) {
if (diff >= step) {
nibble |= mask;
diff -= step;
vpdiff += step;
}
step >>= 1;
mask >>= 1;
}
if (nibble & 8) {
prev -= vpdiff;
} else {
prev += vpdiff;
}
prev = CLAMP(prev, -32768, 32767);
step_idx += _ima_adpcm_index_table[nibble];
step_idx = CLAMP(step_idx, 0, 88);
if (i & 1) {
*out |= nibble << 4;
out++;
} else {
*out = nibble;
}
}
}
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
ResourceImporterWAV();
};
#endif // RESOURCE_IMPORTER_WAV_H