virtualx-engine/scene/resources/importer_mesh.cpp
K. S. Ernest (iFire) Lee 1463fc889b GLTF for game templates.
Convert GLTF Document to use ImporterMeshInstance3D.

Add a GLTFDocument extension list and an extension for converting the importer mesh instance 3d to mesh instance 3d.

Use GLTF module when the editor tools are disabled.

Modified the render server to be less restrictive on matching blend arrays and have more logging.

Misc bugs with multimesh.

Always index the meshes.
2021-10-03 12:37:52 -07:00

1242 lines
42 KiB
C++

/*************************************************************************/
/* importer_mesh.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "importer_mesh.h"
#include "core/math/random_pcg.h"
#include "core/math/static_raycaster.h"
#include "scene/resources/surface_tool.h"
#include <cstdint>
void ImporterMesh::Surface::split_normals(const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals) {
ERR_FAIL_COND(arrays.size() != RS::ARRAY_MAX);
const PackedVector3Array &vertices = arrays[RS::ARRAY_VERTEX];
int current_vertex_count = vertices.size();
int new_vertex_count = p_indices.size();
int final_vertex_count = current_vertex_count + new_vertex_count;
const int *indices_ptr = p_indices.ptr();
for (int i = 0; i < arrays.size(); i++) {
if (i == RS::ARRAY_INDEX) {
continue;
}
if (arrays[i].get_type() == Variant::NIL) {
continue;
}
switch (arrays[i].get_type()) {
case Variant::PACKED_VECTOR3_ARRAY: {
PackedVector3Array data = arrays[i];
data.resize(final_vertex_count);
Vector3 *data_ptr = data.ptrw();
if (i == RS::ARRAY_NORMAL) {
const Vector3 *normals_ptr = p_normals.ptr();
memcpy(&data_ptr[current_vertex_count], normals_ptr, sizeof(Vector3) * new_vertex_count);
} else {
for (int j = 0; j < new_vertex_count; j++) {
data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
}
}
arrays[i] = data;
} break;
case Variant::PACKED_VECTOR2_ARRAY: {
PackedVector2Array data = arrays[i];
data.resize(final_vertex_count);
Vector2 *data_ptr = data.ptrw();
for (int j = 0; j < new_vertex_count; j++) {
data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
}
arrays[i] = data;
} break;
case Variant::PACKED_FLOAT32_ARRAY: {
PackedFloat32Array data = arrays[i];
int elements = data.size() / current_vertex_count;
data.resize(final_vertex_count * elements);
float *data_ptr = data.ptrw();
for (int j = 0; j < new_vertex_count; j++) {
memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(float) * elements);
}
arrays[i] = data;
} break;
case Variant::PACKED_INT32_ARRAY: {
PackedInt32Array data = arrays[i];
int elements = data.size() / current_vertex_count;
data.resize(final_vertex_count * elements);
int32_t *data_ptr = data.ptrw();
for (int j = 0; j < new_vertex_count; j++) {
memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(int32_t) * elements);
}
arrays[i] = data;
} break;
case Variant::PACKED_BYTE_ARRAY: {
PackedByteArray data = arrays[i];
int elements = data.size() / current_vertex_count;
data.resize(final_vertex_count * elements);
uint8_t *data_ptr = data.ptrw();
for (int j = 0; j < new_vertex_count; j++) {
memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(uint8_t) * elements);
}
arrays[i] = data;
} break;
case Variant::PACKED_COLOR_ARRAY: {
PackedColorArray data = arrays[i];
data.resize(final_vertex_count);
Color *data_ptr = data.ptrw();
for (int j = 0; j < new_vertex_count; j++) {
data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
}
arrays[i] = data;
} break;
default: {
ERR_FAIL_MSG("Unhandled array type.");
} break;
}
}
}
void ImporterMesh::add_blend_shape(const String &p_name) {
ERR_FAIL_COND(surfaces.size() > 0);
blend_shapes.push_back(p_name);
}
int ImporterMesh::get_blend_shape_count() const {
return blend_shapes.size();
}
String ImporterMesh::get_blend_shape_name(int p_blend_shape) const {
ERR_FAIL_INDEX_V(p_blend_shape, blend_shapes.size(), String());
return blend_shapes[p_blend_shape];
}
void ImporterMesh::set_blend_shape_mode(Mesh::BlendShapeMode p_blend_shape_mode) {
blend_shape_mode = p_blend_shape_mode;
}
Mesh::BlendShapeMode ImporterMesh::get_blend_shape_mode() const {
return blend_shape_mode;
}
void ImporterMesh::add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, const Dictionary &p_lods, const Ref<Material> &p_material, const String &p_name, const uint32_t p_flags) {
ERR_FAIL_COND(p_blend_shapes.size() != blend_shapes.size());
ERR_FAIL_COND(p_arrays.size() != Mesh::ARRAY_MAX);
Surface s;
s.primitive = p_primitive;
s.arrays = p_arrays;
s.name = p_name;
s.flags = p_flags;
Vector<Vector3> vertex_array = p_arrays[Mesh::ARRAY_VERTEX];
int vertex_count = vertex_array.size();
ERR_FAIL_COND(vertex_count == 0);
for (int i = 0; i < blend_shapes.size(); i++) {
Array bsdata = p_blend_shapes[i];
ERR_FAIL_COND(bsdata.size() != Mesh::ARRAY_MAX);
Vector<Vector3> vertex_data = bsdata[Mesh::ARRAY_VERTEX];
ERR_FAIL_COND(vertex_data.size() != vertex_count);
Surface::BlendShape bs;
bs.arrays = bsdata;
s.blend_shape_data.push_back(bs);
}
List<Variant> lods;
p_lods.get_key_list(&lods);
for (const Variant &E : lods) {
ERR_CONTINUE(!E.is_num());
Surface::LOD lod;
lod.distance = E;
lod.indices = p_lods[E];
ERR_CONTINUE(lod.indices.size() == 0);
s.lods.push_back(lod);
}
s.material = p_material;
surfaces.push_back(s);
mesh.unref();
}
int ImporterMesh::get_surface_count() const {
return surfaces.size();
}
Mesh::PrimitiveType ImporterMesh::get_surface_primitive_type(int p_surface) {
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Mesh::PRIMITIVE_MAX);
return surfaces[p_surface].primitive;
}
Array ImporterMesh::get_surface_arrays(int p_surface) const {
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array());
return surfaces[p_surface].arrays;
}
String ImporterMesh::get_surface_name(int p_surface) const {
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), String());
return surfaces[p_surface].name;
}
void ImporterMesh::set_surface_name(int p_surface, const String &p_name) {
ERR_FAIL_INDEX(p_surface, surfaces.size());
surfaces.write[p_surface].name = p_name;
mesh.unref();
}
Array ImporterMesh::get_surface_blend_shape_arrays(int p_surface, int p_blend_shape) const {
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array());
ERR_FAIL_INDEX_V(p_blend_shape, surfaces[p_surface].blend_shape_data.size(), Array());
return surfaces[p_surface].blend_shape_data[p_blend_shape].arrays;
}
int ImporterMesh::get_surface_lod_count(int p_surface) const {
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0);
return surfaces[p_surface].lods.size();
}
Vector<int> ImporterMesh::get_surface_lod_indices(int p_surface, int p_lod) const {
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Vector<int>());
ERR_FAIL_INDEX_V(p_lod, surfaces[p_surface].lods.size(), Vector<int>());
return surfaces[p_surface].lods[p_lod].indices;
}
float ImporterMesh::get_surface_lod_size(int p_surface, int p_lod) const {
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0);
ERR_FAIL_INDEX_V(p_lod, surfaces[p_surface].lods.size(), 0);
return surfaces[p_surface].lods[p_lod].distance;
}
uint32_t ImporterMesh::get_surface_format(int p_surface) const {
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0);
return surfaces[p_surface].flags;
}
Ref<Material> ImporterMesh::get_surface_material(int p_surface) const {
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Ref<Material>());
return surfaces[p_surface].material;
}
void ImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_material) {
ERR_FAIL_INDEX(p_surface, surfaces.size());
surfaces.write[p_surface].material = p_material;
mesh.unref();
}
void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle) {
if (!SurfaceTool::simplify_scale_func) {
return;
}
if (!SurfaceTool::simplify_with_attrib_func) {
return;
}
if (!SurfaceTool::optimize_vertex_cache_func) {
return;
}
for (int i = 0; i < surfaces.size(); i++) {
if (surfaces[i].primitive != Mesh::PRIMITIVE_TRIANGLES) {
continue;
}
if (get_blend_shape_count()) {
continue;
}
surfaces.write[i].lods.clear();
Vector<Vector3> vertices = surfaces[i].arrays[RS::ARRAY_VERTEX];
PackedInt32Array indices = surfaces[i].arrays[RS::ARRAY_INDEX];
Vector<Vector3> normals = surfaces[i].arrays[RS::ARRAY_NORMAL];
Vector<Vector2> uvs = surfaces[i].arrays[RS::ARRAY_TEX_UV];
unsigned int index_count = indices.size();
unsigned int vertex_count = vertices.size();
if (index_count == 0) {
continue; //no lods if no indices
}
const Vector3 *vertices_ptr = vertices.ptr();
const int *indices_ptr = indices.ptr();
if (normals.is_empty()) {
normals.resize(vertices.size());
Vector3 *n_ptr = normals.ptrw();
for (unsigned int j = 0; j < index_count; j += 3) {
const Vector3 &v0 = vertices_ptr[indices_ptr[j + 0]];
const Vector3 &v1 = vertices_ptr[indices_ptr[j + 1]];
const Vector3 &v2 = vertices_ptr[indices_ptr[j + 2]];
Vector3 n = vec3_cross(v0 - v2, v0 - v1).normalized();
n_ptr[j + 0] = n;
n_ptr[j + 1] = n;
n_ptr[j + 2] = n;
}
}
float normal_merge_threshold = Math::cos(Math::deg2rad(p_normal_merge_angle));
float normal_pre_split_threshold = Math::cos(Math::deg2rad(MIN(180.0f, p_normal_split_angle * 2.0f)));
float normal_split_threshold = Math::cos(Math::deg2rad(p_normal_split_angle));
const Vector3 *normals_ptr = normals.ptr();
Map<Vector3, LocalVector<Pair<int, int>>> unique_vertices;
LocalVector<int> vertex_remap;
LocalVector<int> vertex_inverse_remap;
LocalVector<Vector3> merged_vertices;
LocalVector<Vector3> merged_normals;
LocalVector<int> merged_normals_counts;
const Vector2 *uvs_ptr = uvs.ptr();
for (unsigned int j = 0; j < vertex_count; j++) {
const Vector3 &v = vertices_ptr[j];
const Vector3 &n = normals_ptr[j];
Map<Vector3, LocalVector<Pair<int, int>>>::Element *E = unique_vertices.find(v);
if (E) {
const LocalVector<Pair<int, int>> &close_verts = E->get();
bool found = false;
for (unsigned int k = 0; k < close_verts.size(); k++) {
const Pair<int, int> &idx = close_verts[k];
// TODO check more attributes?
if ((!uvs_ptr || uvs_ptr[j].distance_squared_to(uvs_ptr[idx.second]) < CMP_EPSILON2) && normals[idx.second].dot(n) > normal_merge_threshold) {
vertex_remap.push_back(idx.first);
merged_normals[idx.first] += normals[idx.second];
merged_normals_counts[idx.first]++;
found = true;
break;
}
}
if (!found) {
int vcount = merged_vertices.size();
unique_vertices[v].push_back(Pair<int, int>(vcount, j));
vertex_inverse_remap.push_back(j);
merged_vertices.push_back(v);
vertex_remap.push_back(vcount);
merged_normals.push_back(normals_ptr[j]);
merged_normals_counts.push_back(1);
}
} else {
int vcount = merged_vertices.size();
unique_vertices[v] = LocalVector<Pair<int, int>>();
unique_vertices[v].push_back(Pair<int, int>(vcount, j));
vertex_inverse_remap.push_back(j);
merged_vertices.push_back(v);
vertex_remap.push_back(vcount);
merged_normals.push_back(normals_ptr[j]);
merged_normals_counts.push_back(1);
}
}
LocalVector<int> merged_indices;
merged_indices.resize(index_count);
for (unsigned int j = 0; j < index_count; j++) {
merged_indices[j] = vertex_remap[indices[j]];
}
unsigned int merged_vertex_count = merged_vertices.size();
const Vector3 *merged_vertices_ptr = merged_vertices.ptr();
const int32_t *merged_indices_ptr = merged_indices.ptr();
{
const int *counts_ptr = merged_normals_counts.ptr();
Vector3 *merged_normals_ptrw = merged_normals.ptr();
for (unsigned int j = 0; j < merged_vertex_count; j++) {
merged_normals_ptrw[j] /= counts_ptr[j];
}
}
LocalVector<float> normal_weights;
normal_weights.resize(merged_vertex_count);
for (unsigned int j = 0; j < merged_vertex_count; j++) {
normal_weights[j] = 2.0; // Give some weight to normal preservation, may be worth exposing as an import setting
}
const float max_mesh_error = FLT_MAX; // We don't want to limit by error, just by index target
float scale = SurfaceTool::simplify_scale_func((const float *)merged_vertices_ptr, merged_vertex_count, sizeof(Vector3));
float mesh_error = 0.0f;
unsigned int index_target = 12; // Start with the smallest target, 4 triangles
unsigned int last_index_count = 0;
int split_vertex_count = vertex_count;
LocalVector<Vector3> split_vertex_normals;
LocalVector<int> split_vertex_indices;
split_vertex_normals.reserve(index_count / 3);
split_vertex_indices.reserve(index_count / 3);
RandomPCG pcg;
pcg.seed(123456789); // Keep seed constant across imports
Ref<StaticRaycaster> raycaster = StaticRaycaster::create();
if (raycaster.is_valid()) {
raycaster->add_mesh(vertices, indices, 0);
raycaster->commit();
}
while (index_target < index_count) {
PackedInt32Array new_indices;
new_indices.resize(index_count);
size_t new_index_count = SurfaceTool::simplify_with_attrib_func((unsigned int *)new_indices.ptrw(), (const uint32_t *)merged_indices_ptr, index_count, (const float *)merged_vertices_ptr, merged_vertex_count, sizeof(Vector3), index_target, max_mesh_error, &mesh_error, (float *)merged_normals.ptr(), normal_weights.ptr(), 3);
if (new_index_count < last_index_count * 1.5f) {
index_target = index_target * 1.5f;
continue;
}
if (new_index_count <= 0 || (new_index_count >= (index_count * 0.75f))) {
break;
}
new_indices.resize(new_index_count);
LocalVector<LocalVector<int>> vertex_corners;
vertex_corners.resize(vertex_count);
{
int *ptrw = new_indices.ptrw();
for (unsigned int j = 0; j < new_index_count; j++) {
const int &remapped = vertex_inverse_remap[ptrw[j]];
vertex_corners[remapped].push_back(j);
ptrw[j] = remapped;
}
}
if (raycaster.is_valid()) {
float error_factor = 1.0f / (scale * MAX(mesh_error, 0.15));
const float ray_bias = 0.05;
float ray_length = ray_bias + mesh_error * scale * 3.0f;
Vector<StaticRaycaster::Ray> rays;
LocalVector<Vector2> ray_uvs;
int32_t *new_indices_ptr = new_indices.ptrw();
int current_ray_count = 0;
for (unsigned int j = 0; j < new_index_count; j += 3) {
const Vector3 &v0 = vertices_ptr[new_indices_ptr[j + 0]];
const Vector3 &v1 = vertices_ptr[new_indices_ptr[j + 1]];
const Vector3 &v2 = vertices_ptr[new_indices_ptr[j + 2]];
Vector3 face_normal = vec3_cross(v0 - v2, v0 - v1);
float face_area = face_normal.length(); // Actually twice the face area, since it's the same error_factor on all faces, we don't care
Vector3 dir = face_normal / face_area;
int ray_count = CLAMP(5.0 * face_area * error_factor, 16, 64);
rays.resize(current_ray_count + ray_count);
StaticRaycaster::Ray *rays_ptr = rays.ptrw();
ray_uvs.resize(current_ray_count + ray_count);
Vector2 *ray_uvs_ptr = ray_uvs.ptr();
for (int k = 0; k < ray_count; k++) {
float u = pcg.randf();
float v = pcg.randf();
if (u + v >= 1.0f) {
u = 1.0f - u;
v = 1.0f - v;
}
u = 0.9f * u + 0.05f / 3.0f; // Give barycentric coordinates some padding, we don't want to sample right on the edge
v = 0.9f * v + 0.05f / 3.0f; // v = (v - one_third) * 0.95f + one_third;
float w = 1.0f - u - v;
Vector3 org = v0 * w + v1 * u + v2 * v;
org -= dir * ray_bias;
rays_ptr[current_ray_count + k] = StaticRaycaster::Ray(org, dir, 0.0f, ray_length);
rays_ptr[current_ray_count + k].id = j / 3;
ray_uvs_ptr[current_ray_count + k] = Vector2(u, v);
}
current_ray_count += ray_count;
}
raycaster->intersect(rays);
LocalVector<Vector3> ray_normals;
LocalVector<float> ray_normal_weights;
ray_normals.resize(new_index_count);
ray_normal_weights.resize(new_index_count);
for (unsigned int j = 0; j < new_index_count; j++) {
ray_normal_weights[j] = 0.0f;
}
const StaticRaycaster::Ray *rp = rays.ptr();
for (int j = 0; j < rays.size(); j++) {
if (rp[j].geomID != 0) { // Ray missed
continue;
}
if (rp[j].normal.normalized().dot(rp[j].dir) > 0.0f) { // Hit a back face.
continue;
}
const float &u = rp[j].u;
const float &v = rp[j].v;
const float w = 1.0f - u - v;
const unsigned int &hit_tri_id = rp[j].primID;
const unsigned int &orig_tri_id = rp[j].id;
const Vector3 &n0 = normals_ptr[indices_ptr[hit_tri_id * 3 + 0]];
const Vector3 &n1 = normals_ptr[indices_ptr[hit_tri_id * 3 + 1]];
const Vector3 &n2 = normals_ptr[indices_ptr[hit_tri_id * 3 + 2]];
Vector3 normal = n0 * w + n1 * u + n2 * v;
Vector2 orig_uv = ray_uvs[j];
float orig_bary[3] = { 1.0f - orig_uv.x - orig_uv.y, orig_uv.x, orig_uv.y };
for (int k = 0; k < 3; k++) {
int idx = orig_tri_id * 3 + k;
float weight = orig_bary[k];
ray_normals[idx] += normal * weight;
ray_normal_weights[idx] += weight;
}
}
for (unsigned int j = 0; j < new_index_count; j++) {
if (ray_normal_weights[j] < 1.0f) { // Not enough data, the new normal would be just a bad guess
ray_normals[j] = Vector3();
} else {
ray_normals[j] /= ray_normal_weights[j];
}
}
LocalVector<LocalVector<int>> normal_group_indices;
LocalVector<Vector3> normal_group_averages;
normal_group_indices.reserve(24);
normal_group_averages.reserve(24);
for (unsigned int j = 0; j < vertex_count; j++) {
const LocalVector<int> &corners = vertex_corners[j];
const Vector3 &vertex_normal = normals_ptr[j];
for (unsigned int k = 0; k < corners.size(); k++) {
const int &corner_idx = corners[k];
const Vector3 &ray_normal = ray_normals[corner_idx];
if (ray_normal.length_squared() < CMP_EPSILON2) {
continue;
}
bool found = false;
for (unsigned int l = 0; l < normal_group_indices.size(); l++) {
LocalVector<int> &group_indices = normal_group_indices[l];
Vector3 n = normal_group_averages[l] / group_indices.size();
if (n.dot(ray_normal) > normal_pre_split_threshold) {
found = true;
group_indices.push_back(corner_idx);
normal_group_averages[l] += ray_normal;
break;
}
}
if (!found) {
LocalVector<int> new_group;
new_group.push_back(corner_idx);
normal_group_indices.push_back(new_group);
normal_group_averages.push_back(ray_normal);
}
}
for (unsigned int k = 0; k < normal_group_indices.size(); k++) {
LocalVector<int> &group_indices = normal_group_indices[k];
Vector3 n = normal_group_averages[k] / group_indices.size();
if (vertex_normal.dot(n) < normal_split_threshold) {
split_vertex_indices.push_back(j);
split_vertex_normals.push_back(n);
int new_idx = split_vertex_count++;
for (unsigned int l = 0; l < group_indices.size(); l++) {
new_indices_ptr[group_indices[l]] = new_idx;
}
}
}
normal_group_indices.clear();
normal_group_averages.clear();
}
}
Surface::LOD lod;
lod.distance = MAX(mesh_error * scale, CMP_EPSILON2);
lod.indices = new_indices;
surfaces.write[i].lods.push_back(lod);
index_target = MAX(new_index_count, index_target) * 2;
last_index_count = new_index_count;
if (mesh_error == 0.0f) {
break;
}
}
surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals);
surfaces.write[i].lods.sort_custom<Surface::LODComparator>();
for (int j = 0; j < surfaces.write[i].lods.size(); j++) {
Surface::LOD &lod = surfaces.write[i].lods.write[j];
unsigned int *lod_indices_ptr = (unsigned int *)lod.indices.ptrw();
SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), split_vertex_count);
}
}
}
bool ImporterMesh::has_mesh() const {
return mesh.is_valid();
}
Ref<ArrayMesh> ImporterMesh::get_mesh(const Ref<ArrayMesh> &p_base) {
ERR_FAIL_COND_V(surfaces.size() == 0, Ref<ArrayMesh>());
if (mesh.is_null()) {
if (p_base.is_valid()) {
mesh = p_base;
}
if (mesh.is_null()) {
mesh.instantiate();
}
mesh->set_name(get_name());
if (has_meta("import_id")) {
mesh->set_meta("import_id", get_meta("import_id"));
}
for (int i = 0; i < blend_shapes.size(); i++) {
mesh->add_blend_shape(blend_shapes[i]);
}
mesh->set_blend_shape_mode(blend_shape_mode);
for (int i = 0; i < surfaces.size(); i++) {
Array bs_data;
if (surfaces[i].blend_shape_data.size()) {
for (int j = 0; j < surfaces[i].blend_shape_data.size(); j++) {
bs_data.push_back(surfaces[i].blend_shape_data[j].arrays);
}
}
Dictionary lods;
if (surfaces[i].lods.size()) {
for (int j = 0; j < surfaces[i].lods.size(); j++) {
lods[surfaces[i].lods[j].distance] = surfaces[i].lods[j].indices;
}
}
mesh->add_surface_from_arrays(surfaces[i].primitive, surfaces[i].arrays, bs_data, lods, surfaces[i].flags);
if (surfaces[i].material.is_valid()) {
mesh->surface_set_material(mesh->get_surface_count() - 1, surfaces[i].material);
}
if (surfaces[i].name != String()) {
mesh->surface_set_name(mesh->get_surface_count() - 1, surfaces[i].name);
}
}
mesh->set_lightmap_size_hint(lightmap_size_hint);
if (shadow_mesh.is_valid()) {
Ref<ArrayMesh> shadow = shadow_mesh->get_mesh();
mesh->set_shadow_mesh(shadow);
}
}
return mesh;
}
void ImporterMesh::clear() {
surfaces.clear();
blend_shapes.clear();
mesh.unref();
}
void ImporterMesh::create_shadow_mesh() {
if (shadow_mesh.is_valid()) {
shadow_mesh.unref();
}
//no shadow mesh for blendshapes
if (blend_shapes.size() > 0) {
return;
}
//no shadow mesh for skeletons
for (int i = 0; i < surfaces.size(); i++) {
if (surfaces[i].arrays[RS::ARRAY_BONES].get_type() != Variant::NIL) {
return;
}
if (surfaces[i].arrays[RS::ARRAY_WEIGHTS].get_type() != Variant::NIL) {
return;
}
}
shadow_mesh.instantiate();
for (int i = 0; i < surfaces.size(); i++) {
LocalVector<int> vertex_remap;
Vector<Vector3> new_vertices;
Vector<Vector3> vertices = surfaces[i].arrays[RS::ARRAY_VERTEX];
int vertex_count = vertices.size();
{
Map<Vector3, int> unique_vertices;
const Vector3 *vptr = vertices.ptr();
for (int j = 0; j < vertex_count; j++) {
const Vector3 &v = vptr[j];
Map<Vector3, int>::Element *E = unique_vertices.find(v);
if (E) {
vertex_remap.push_back(E->get());
} else {
int vcount = unique_vertices.size();
unique_vertices[v] = vcount;
vertex_remap.push_back(vcount);
new_vertices.push_back(v);
}
}
}
Array new_surface;
new_surface.resize(RS::ARRAY_MAX);
Dictionary lods;
// print_line("original vertex count: " + itos(vertices.size()) + " new vertex count: " + itos(new_vertices.size()));
new_surface[RS::ARRAY_VERTEX] = new_vertices;
Vector<int> indices = surfaces[i].arrays[RS::ARRAY_INDEX];
if (indices.size()) {
int index_count = indices.size();
const int *index_rptr = indices.ptr();
Vector<int> new_indices;
new_indices.resize(indices.size());
int *index_wptr = new_indices.ptrw();
for (int j = 0; j < index_count; j++) {
int index = index_rptr[j];
ERR_FAIL_INDEX(index, vertex_count);
index_wptr[j] = vertex_remap[index];
}
new_surface[RS::ARRAY_INDEX] = new_indices;
// Make sure the same LODs as the full version are used.
// This makes it more coherent between rendered model and its shadows.
for (int j = 0; j < surfaces[i].lods.size(); j++) {
indices = surfaces[i].lods[j].indices;
index_count = indices.size();
index_rptr = indices.ptr();
new_indices.resize(indices.size());
index_wptr = new_indices.ptrw();
for (int k = 0; k < index_count; k++) {
int index = index_rptr[k];
ERR_FAIL_INDEX(index, vertex_count);
index_wptr[k] = vertex_remap[index];
}
lods[surfaces[i].lods[j].distance] = new_indices;
}
}
shadow_mesh->add_surface(surfaces[i].primitive, new_surface, Array(), lods, Ref<Material>(), surfaces[i].name, surfaces[i].flags);
}
}
Ref<ImporterMesh> ImporterMesh::get_shadow_mesh() const {
return shadow_mesh;
}
void ImporterMesh::_set_data(const Dictionary &p_data) {
clear();
if (p_data.has("blend_shape_names")) {
blend_shapes = p_data["blend_shape_names"];
}
if (p_data.has("surfaces")) {
Array surface_arr = p_data["surfaces"];
for (int i = 0; i < surface_arr.size(); i++) {
Dictionary s = surface_arr[i];
ERR_CONTINUE(!s.has("primitive"));
ERR_CONTINUE(!s.has("arrays"));
Mesh::PrimitiveType prim = Mesh::PrimitiveType(int(s["primitive"]));
ERR_CONTINUE(prim >= Mesh::PRIMITIVE_MAX);
Array arr = s["arrays"];
Dictionary lods;
String name;
if (s.has("name")) {
name = s["name"];
}
if (s.has("lods")) {
lods = s["lods"];
}
Array b_shapes;
if (s.has("b_shapes")) {
b_shapes = s["b_shapes"];
}
Ref<Material> material;
if (s.has("material")) {
material = s["material"];
}
uint32_t flags = 0;
if (s.has("flags")) {
flags = s["flags"];
}
add_surface(prim, arr, b_shapes, lods, material, name, flags);
}
}
}
Dictionary ImporterMesh::_get_data() const {
Dictionary data;
if (blend_shapes.size()) {
data["blend_shape_names"] = blend_shapes;
}
Array surface_arr;
for (int i = 0; i < surfaces.size(); i++) {
Dictionary d;
d["primitive"] = surfaces[i].primitive;
d["arrays"] = surfaces[i].arrays;
if (surfaces[i].blend_shape_data.size()) {
Array bs_data;
for (int j = 0; j < surfaces[i].blend_shape_data.size(); j++) {
bs_data.push_back(surfaces[i].blend_shape_data[j].arrays);
}
d["blend_shapes"] = bs_data;
}
if (surfaces[i].lods.size()) {
Dictionary lods;
for (int j = 0; j < surfaces[i].lods.size(); j++) {
lods[surfaces[i].lods[j].distance] = surfaces[i].lods[j].indices;
}
d["lods"] = lods;
}
if (surfaces[i].material.is_valid()) {
d["material"] = surfaces[i].material;
}
if (surfaces[i].name != String()) {
d["name"] = surfaces[i].name;
}
if (surfaces[i].flags != 0) {
d["flags"] = surfaces[i].flags;
}
surface_arr.push_back(d);
}
data["surfaces"] = surface_arr;
return data;
}
Vector<Face3> ImporterMesh::get_faces() const {
Vector<Face3> faces;
for (int i = 0; i < surfaces.size(); i++) {
if (surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) {
Vector<Vector3> vertices = surfaces[i].arrays[Mesh::ARRAY_VERTEX];
Vector<int> indices = surfaces[i].arrays[Mesh::ARRAY_INDEX];
if (indices.size()) {
for (int j = 0; j < indices.size(); j += 3) {
Face3 f;
f.vertex[0] = vertices[indices[j + 0]];
f.vertex[1] = vertices[indices[j + 1]];
f.vertex[2] = vertices[indices[j + 2]];
faces.push_back(f);
}
} else {
for (int j = 0; j < vertices.size(); j += 3) {
Face3 f;
f.vertex[0] = vertices[j + 0];
f.vertex[1] = vertices[j + 1];
f.vertex[2] = vertices[j + 2];
faces.push_back(f);
}
}
}
}
return faces;
}
Vector<Ref<Shape3D>> ImporterMesh::convex_decompose(const Mesh::ConvexDecompositionSettings &p_settings) const {
ERR_FAIL_COND_V(!Mesh::convex_decomposition_function, Vector<Ref<Shape3D>>());
const Vector<Face3> faces = get_faces();
int face_count = faces.size();
Vector<Vector3> vertices;
uint32_t vertex_count = 0;
vertices.resize(face_count * 3);
Vector<uint32_t> indices;
indices.resize(face_count * 3);
{
Map<Vector3, uint32_t> vertex_map;
Vector3 *vertex_w = vertices.ptrw();
uint32_t *index_w = indices.ptrw();
for (int i = 0; i < face_count; i++) {
for (int j = 0; j < 3; j++) {
const Vector3 &vertex = faces[i].vertex[j];
Map<Vector3, uint32_t>::Element *found_vertex = vertex_map.find(vertex);
uint32_t index;
if (found_vertex) {
index = found_vertex->get();
} else {
index = ++vertex_count;
vertex_map[vertex] = index;
vertex_w[index] = vertex;
}
index_w[i * 3 + j] = index;
}
}
}
vertices.resize(vertex_count);
Vector<Vector<Vector3>> decomposed = Mesh::convex_decomposition_function((real_t *)vertices.ptr(), vertex_count, indices.ptr(), face_count, p_settings, nullptr);
Vector<Ref<Shape3D>> ret;
for (int i = 0; i < decomposed.size(); i++) {
Ref<ConvexPolygonShape3D> shape;
shape.instantiate();
shape->set_points(decomposed[i]);
ret.push_back(shape);
}
return ret;
}
Ref<Shape3D> ImporterMesh::create_trimesh_shape() const {
Vector<Face3> faces = get_faces();
if (faces.size() == 0) {
return Ref<Shape3D>();
}
Vector<Vector3> face_points;
face_points.resize(faces.size() * 3);
for (int i = 0; i < face_points.size(); i += 3) {
Face3 f = faces.get(i / 3);
face_points.set(i, f.vertex[0]);
face_points.set(i + 1, f.vertex[1]);
face_points.set(i + 2, f.vertex[2]);
}
Ref<ConcavePolygonShape3D> shape = memnew(ConcavePolygonShape3D);
shape->set_faces(face_points);
return shape;
}
Ref<NavigationMesh> ImporterMesh::create_navigation_mesh() {
Vector<Face3> faces = get_faces();
if (faces.size() == 0) {
return Ref<NavigationMesh>();
}
Map<Vector3, int> unique_vertices;
LocalVector<int> face_indices;
for (int i = 0; i < faces.size(); i++) {
for (int j = 0; j < 3; j++) {
Vector3 v = faces[i].vertex[j];
int idx;
if (unique_vertices.has(v)) {
idx = unique_vertices[v];
} else {
idx = unique_vertices.size();
unique_vertices[v] = idx;
}
face_indices.push_back(idx);
}
}
Vector<Vector3> vertices;
vertices.resize(unique_vertices.size());
for (const KeyValue<Vector3, int> &E : unique_vertices) {
vertices.write[E.value] = E.key;
}
Ref<NavigationMesh> nm;
nm.instantiate();
nm->set_vertices(vertices);
Vector<int> v3;
v3.resize(3);
for (uint32_t i = 0; i < face_indices.size(); i += 3) {
v3.write[0] = face_indices[i + 0];
v3.write[1] = face_indices[i + 1];
v3.write[2] = face_indices[i + 2];
nm->add_polygon(v3);
}
return nm;
}
extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y);
struct EditorSceneImporterMeshLightmapSurface {
Ref<Material> material;
LocalVector<SurfaceTool::Vertex> vertices;
Mesh::PrimitiveType primitive = Mesh::PrimitiveType::PRIMITIVE_MAX;
uint32_t format = 0;
String name;
};
Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache) {
ERR_FAIL_COND_V(!array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED);
ERR_FAIL_COND_V_MSG(blend_shapes.size() != 0, ERR_UNAVAILABLE, "Can't unwrap mesh with blend shapes.");
LocalVector<float> vertices;
LocalVector<float> normals;
LocalVector<int> indices;
LocalVector<float> uv;
LocalVector<Pair<int, int>> uv_indices;
Vector<EditorSceneImporterMeshLightmapSurface> lightmap_surfaces;
// Keep only the scale
Basis basis = p_base_transform.get_basis();
Vector3 scale = Vector3(basis.get_axis(0).length(), basis.get_axis(1).length(), basis.get_axis(2).length());
Transform3D transform;
transform.scale(scale);
Basis normal_basis = transform.basis.inverse().transposed();
for (int i = 0; i < get_surface_count(); i++) {
EditorSceneImporterMeshLightmapSurface s;
s.primitive = get_surface_primitive_type(i);
ERR_FAIL_COND_V_MSG(s.primitive != Mesh::PRIMITIVE_TRIANGLES, ERR_UNAVAILABLE, "Only triangles are supported for lightmap unwrap.");
Array arrays = get_surface_arrays(i);
s.material = get_surface_material(i);
s.name = get_surface_name(i);
SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format);
PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX];
int vc = rvertices.size();
PackedVector3Array rnormals = arrays[Mesh::ARRAY_NORMAL];
int vertex_ofs = vertices.size() / 3;
vertices.resize((vertex_ofs + vc) * 3);
normals.resize((vertex_ofs + vc) * 3);
uv_indices.resize(vertex_ofs + vc);
for (int j = 0; j < vc; j++) {
Vector3 v = transform.xform(rvertices[j]);
Vector3 n = normal_basis.xform(rnormals[j]).normalized();
vertices[(j + vertex_ofs) * 3 + 0] = v.x;
vertices[(j + vertex_ofs) * 3 + 1] = v.y;
vertices[(j + vertex_ofs) * 3 + 2] = v.z;
normals[(j + vertex_ofs) * 3 + 0] = n.x;
normals[(j + vertex_ofs) * 3 + 1] = n.y;
normals[(j + vertex_ofs) * 3 + 2] = n.z;
uv_indices[j + vertex_ofs] = Pair<int, int>(i, j);
}
PackedInt32Array rindices = arrays[Mesh::ARRAY_INDEX];
int ic = rindices.size();
float eps = 1.19209290e-7F; // Taken from xatlas.h
if (ic == 0) {
for (int j = 0; j < vc / 3; j++) {
Vector3 p0 = transform.xform(rvertices[j * 3 + 0]);
Vector3 p1 = transform.xform(rvertices[j * 3 + 1]);
Vector3 p2 = transform.xform(rvertices[j * 3 + 2]);
if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) {
continue;
}
indices.push_back(vertex_ofs + j * 3 + 0);
indices.push_back(vertex_ofs + j * 3 + 1);
indices.push_back(vertex_ofs + j * 3 + 2);
}
} else {
for (int j = 0; j < ic / 3; j++) {
Vector3 p0 = transform.xform(rvertices[rindices[j * 3 + 0]]);
Vector3 p1 = transform.xform(rvertices[rindices[j * 3 + 1]]);
Vector3 p2 = transform.xform(rvertices[rindices[j * 3 + 2]]);
if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) {
continue;
}
indices.push_back(vertex_ofs + rindices[j * 3 + 0]);
indices.push_back(vertex_ofs + rindices[j * 3 + 1]);
indices.push_back(vertex_ofs + rindices[j * 3 + 2]);
}
}
lightmap_surfaces.push_back(s);
}
//unwrap
bool use_cache = true; // Used to request cache generation and to know if cache was used
uint8_t *gen_cache;
int gen_cache_size;
float *gen_uvs;
int *gen_vertices;
int *gen_indices;
int gen_vertex_count;
int gen_index_count;
int size_x;
int size_y;
bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), p_src_cache.ptr(), &use_cache, &gen_cache, &gen_cache_size, &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y);
if (!ok) {
return ERR_CANT_CREATE;
}
//remove surfaces
clear();
//create surfacetools for each surface..
LocalVector<Ref<SurfaceTool>> surfaces_tools;
for (int i = 0; i < lightmap_surfaces.size(); i++) {
Ref<SurfaceTool> st;
st.instantiate();
st->begin(Mesh::PRIMITIVE_TRIANGLES);
st->set_material(lightmap_surfaces[i].material);
st->set_meta("name", lightmap_surfaces[i].name);
surfaces_tools.push_back(st); //stay there
}
print_verbose("Mesh: Gen indices: " + itos(gen_index_count));
//go through all indices
for (int i = 0; i < gen_index_count; i += 3) {
ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], (int)uv_indices.size(), ERR_BUG);
ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], (int)uv_indices.size(), ERR_BUG);
ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], (int)uv_indices.size(), ERR_BUG);
ERR_FAIL_COND_V(uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 1]]].first || uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG);
int surface = uv_indices[gen_vertices[gen_indices[i + 0]]].first;
for (int j = 0; j < 3; j++) {
SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second];
if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_COLOR) {
surfaces_tools[surface]->set_color(v.color);
}
if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TEX_UV) {
surfaces_tools[surface]->set_uv(v.uv);
}
if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_NORMAL) {
surfaces_tools[surface]->set_normal(v.normal);
}
if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TANGENT) {
Plane t;
t.normal = v.tangent;
t.d = v.binormal.dot(v.normal.cross(v.tangent)) < 0 ? -1 : 1;
surfaces_tools[surface]->set_tangent(t);
}
if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_BONES) {
surfaces_tools[surface]->set_bones(v.bones);
}
if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_WEIGHTS) {
surfaces_tools[surface]->set_weights(v.weights);
}
Vector2 uv2(gen_uvs[gen_indices[i + j] * 2 + 0], gen_uvs[gen_indices[i + j] * 2 + 1]);
surfaces_tools[surface]->set_uv2(uv2);
surfaces_tools[surface]->add_vertex(v.vertex);
}
}
//generate surfaces
for (unsigned int i = 0; i < surfaces_tools.size(); i++) {
surfaces_tools[i]->index();
Array arrays = surfaces_tools[i]->commit_to_arrays();
add_surface(surfaces_tools[i]->get_primitive(), arrays, Array(), Dictionary(), surfaces_tools[i]->get_material(), surfaces_tools[i]->get_meta("name"));
}
set_lightmap_size_hint(Size2(size_x, size_y));
if (gen_cache_size > 0) {
r_dst_cache.resize(gen_cache_size);
memcpy(r_dst_cache.ptrw(), gen_cache, gen_cache_size);
memfree(gen_cache);
}
if (!use_cache) {
// Cache was not used, free the buffers
memfree(gen_vertices);
memfree(gen_indices);
memfree(gen_uvs);
}
return OK;
}
void ImporterMesh::set_lightmap_size_hint(const Size2i &p_size) {
lightmap_size_hint = p_size;
}
Size2i ImporterMesh::get_lightmap_size_hint() const {
return lightmap_size_hint;
}
void ImporterMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_blend_shape", "name"), &ImporterMesh::add_blend_shape);
ClassDB::bind_method(D_METHOD("get_blend_shape_count"), &ImporterMesh::get_blend_shape_count);
ClassDB::bind_method(D_METHOD("get_blend_shape_name", "blend_shape_idx"), &ImporterMesh::get_blend_shape_name);
ClassDB::bind_method(D_METHOD("set_blend_shape_mode", "mode"), &ImporterMesh::set_blend_shape_mode);
ClassDB::bind_method(D_METHOD("get_blend_shape_mode"), &ImporterMesh::get_blend_shape_mode);
ClassDB::bind_method(D_METHOD("add_surface", "primitive", "arrays", "blend_shapes", "lods", "material", "name", "flags"), &ImporterMesh::add_surface, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(Ref<Material>()), DEFVAL(String()), DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_surface_count"), &ImporterMesh::get_surface_count);
ClassDB::bind_method(D_METHOD("get_surface_primitive_type", "surface_idx"), &ImporterMesh::get_surface_primitive_type);
ClassDB::bind_method(D_METHOD("get_surface_name", "surface_idx"), &ImporterMesh::get_surface_name);
ClassDB::bind_method(D_METHOD("get_surface_arrays", "surface_idx"), &ImporterMesh::get_surface_arrays);
ClassDB::bind_method(D_METHOD("get_surface_blend_shape_arrays", "surface_idx", "blend_shape_idx"), &ImporterMesh::get_surface_blend_shape_arrays);
ClassDB::bind_method(D_METHOD("get_surface_lod_count", "surface_idx"), &ImporterMesh::get_surface_lod_count);
ClassDB::bind_method(D_METHOD("get_surface_lod_size", "surface_idx", "lod_idx"), &ImporterMesh::get_surface_lod_size);
ClassDB::bind_method(D_METHOD("get_surface_lod_indices", "surface_idx", "lod_idx"), &ImporterMesh::get_surface_lod_indices);
ClassDB::bind_method(D_METHOD("get_surface_material", "surface_idx"), &ImporterMesh::get_surface_material);
ClassDB::bind_method(D_METHOD("get_surface_format", "surface_idx"), &ImporterMesh::get_surface_format);
ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &ImporterMesh::set_surface_name);
ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &ImporterMesh::set_surface_material);
ClassDB::bind_method(D_METHOD("get_mesh", "base_mesh"), &ImporterMesh::get_mesh, DEFVAL(Ref<ArrayMesh>()));
ClassDB::bind_method(D_METHOD("clear"), &ImporterMesh::clear);
ClassDB::bind_method(D_METHOD("_set_data", "data"), &ImporterMesh::_set_data);
ClassDB::bind_method(D_METHOD("_get_data"), &ImporterMesh::_get_data);
ClassDB::bind_method(D_METHOD("set_lightmap_size_hint", "size"), &ImporterMesh::set_lightmap_size_hint);
ClassDB::bind_method(D_METHOD("get_lightmap_size_hint"), &ImporterMesh::get_lightmap_size_hint);
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_data", "_get_data");
}