Files
test/source/blender/io/collada/MeshImporter.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1185 lines
36 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2010-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup collada
2011-02-27 20:30:35 +00:00
*/
#include <iostream>
#include "COLLADAFWMeshPrimitive.h"
#include "COLLADAFWMeshVertexData.h"
#include "COLLADAFWPolygons.h"
#include "BKE_attribute.hh"
#include "BKE_customdata.hh"
#include "BKE_global.hh"
2024-01-15 12:44:04 -05:00
#include "BKE_lib_id.hh"
#include "BKE_material.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_runtime.hh"
#include "BKE_object.hh"
#include "DNA_meshdata_types.h"
#include "ArmatureImporter.h"
#include "MeshImporter.h"
#include "collada_utils.h"
Mesh: Move positions to a generic attribute **Changes** As described in T93602, this patch removes all use of the `MVert` struct, replacing it with a generic named attribute with the name `"position"`, consistent with other geometry types. Variable names have been changed from `verts` to `positions`, to align with the attribute name and the more generic design (positions are not vertices, they are just an attribute stored on the point domain). This change is made possible by previous commits that moved all other data out of `MVert` to runtime data or other generic attributes. What remains is mostly a simple type change. Though, the type still shows up 859 times, so the patch is quite large. One compromise is that now `CD_MASK_BAREMESH` now contains `CD_PROP_FLOAT3`. With the general move towards generic attributes over custom data types, we are removing use of these type masks anyway. **Benefits** The most obvious benefit is reduced memory usage and the benefits that brings in memory-bound situations. `float3` is only 3 bytes, in comparison to `MVert` which was 4. When there are millions of vertices this starts to matter more. The other benefits come from using a more generic type. Instead of writing algorithms specifically for `MVert`, code can just use arrays of vectors. This will allow eliminating many temporary arrays or wrappers used to extract positions. Many possible improvements aren't implemented in this patch, though I did switch simplify or remove the process of creating temporary position arrays in a few places. The design clarity that "positions are just another attribute" brings allows removing explicit copying of vertices in some procedural operations-- they are just processed like most other attributes. **Performance** This touches so many areas that it's hard to benchmark exhaustively, but I observed some areas as examples. * The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster. * The Spring splash screen went from ~4.3 to ~4.5 fps. * The subdivision surface modifier/node was slightly faster RNA access through Python may be slightly slower, since now we need a name lookup instead of just a custom data type lookup for each index. **Future Improvements** * Remove uses of "vert_coords" functions: * `BKE_mesh_vert_coords_alloc` * `BKE_mesh_vert_coords_get` * `BKE_mesh_vert_coords_apply{_with_mat4}` * Remove more hidden copying of positions * General simplification now possible in many areas * Convert more code to C++ to use `float3` instead of `float[3]` * Currently `reinterpret_cast` is used for those C-API functions Differential Revision: https://developer.blender.org/D15982
2023-01-10 00:10:43 -05:00
using blender::float3;
Mesh: Remove redundant custom data pointers For copy-on-write, we want to share attribute arrays between meshes where possible. Mutable pointers like `Mesh.mvert` make that difficult by making ownership vague. They also make code more complex by adding redundancy. The simplest solution is just removing them and retrieving layers from `CustomData` as needed. Similar changes have already been applied to curves and point clouds (e9f82d3dc7ee, 410a6efb747f). Removing use of the pointers generally makes code more obvious and more reusable. Mesh data is now accessed with a C++ API (`Mesh::edges()` or `Mesh::edges_for_write()`), and a C API (`BKE_mesh_edges(mesh)`). The CoW changes this commit makes possible are described in T95845 and T95842, and started in D14139 and D14140. The change also simplifies the ongoing mesh struct-of-array refactors from T95965. **RNA/Python Access Performance** Theoretically, accessing mesh elements with the RNA API may become slower, since the layer needs to be found on every random access. However, overhead is already high enough that this doesn't make a noticible differenc, and performance is actually improved in some cases. Random access can be up to 10% faster, but other situations might be a bit slower. Generally using `foreach_get/set` are the best way to improve performance. See the differential revision for more discussion about Python performance. Cycles has been updated to use raw pointers and the internal Blender mesh types, mostly because there is no sense in having this overhead when it's already compiled with Blender. In my tests this roughly halves the Cycles mesh creation time (0.19s to 0.10s for a 1 million face grid). Differential Revision: https://developer.blender.org/D15488
2022-09-05 11:56:34 -05:00
using blender::MutableSpan;
/* get node name, or fall back to original id if not present (name is optional) */
template<class T> static std::string bc_get_dae_name(T *node)
{
return node->getName().empty() ? node->getOriginalId() : node->getName();
}
static const char *bc_primTypeToStr(COLLADAFW::MeshPrimitive::PrimitiveType type)
{
switch (type) {
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshPrimitive::LINES:
return "LINES";
case COLLADAFW::MeshPrimitive::LINE_STRIPS:
return "LINESTRIPS";
case COLLADAFW::MeshPrimitive::POLYGONS:
return "POLYGONS";
case COLLADAFW::MeshPrimitive::POLYLIST:
return "POLYLIST";
case COLLADAFW::MeshPrimitive::TRIANGLES:
return "TRIANGLES";
case COLLADAFW::MeshPrimitive::TRIANGLE_FANS:
return "TRIANGLE_FANS";
case COLLADAFW::MeshPrimitive::TRIANGLE_STRIPS:
return "TRIANGLE_STRIPS";
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshPrimitive::POINTS:
return "POINTS";
case COLLADAFW::MeshPrimitive::UNDEFINED_PRIMITIVE_TYPE:
return "UNDEFINED_PRIMITIVE_TYPE";
}
return "UNKNOWN";
}
static const char *bc_geomTypeToStr(COLLADAFW::Geometry::GeometryType type)
{
switch (type) {
2012-06-12 22:05:33 +00:00
case COLLADAFW::Geometry::GEO_TYPE_MESH:
return "MESH";
case COLLADAFW::Geometry::GEO_TYPE_SPLINE:
return "SPLINE";
case COLLADAFW::Geometry::GEO_TYPE_CONVEX_MESH:
return "CONVEX_MESH";
case COLLADAFW::Geometry::GEO_TYPE_UNKNOWN:
default:
return "UNKNOWN";
}
}
UVDataWrapper::UVDataWrapper(COLLADAFW::MeshVertexData &vdata) : mVData(&vdata) {}
#ifdef COLLADA_DEBUG
void WVDataWrapper::print()
{
fprintf(stderr, "UVs:\n");
switch (mVData->getType()) {
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: {
COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues();
if (values->getCount()) {
for (int i = 0; i < values->getCount(); i += 2) {
2012-06-12 22:05:33 +00:00
fprintf(stderr, "%.1f, %.1f\n", (*values)[i], (*values)[i + 1]);
}
}
break;
}
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: {
COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues();
if (values->getCount()) {
for (int i = 0; i < values->getCount(); i += 2) {
fprintf(stderr, "%.1f, %.1f\n", float((*values)[i]), float((*values)[i + 1]));
}
}
break;
}
}
fprintf(stderr, "\n");
}
#endif
void UVDataWrapper::getUV(int uv_index, float *uv)
{
int stride = mVData->getStride(0);
if (stride == 0) {
2012-06-12 22:05:33 +00:00
stride = 2;
}
switch (mVData->getType()) {
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: {
COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues();
if (values->empty()) {
return;
}
2012-06-12 22:05:33 +00:00
uv[0] = (*values)[uv_index * stride];
uv[1] = (*values)[uv_index * stride + 1];
break;
}
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: {
COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues();
if (values->empty()) {
return;
}
uv[0] = float((*values)[uv_index * stride]);
uv[1] = float((*values)[uv_index * stride + 1]);
break;
}
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_UNKNOWN:
default:
fprintf(stderr, "MeshImporter.getUV(): unknown data type\n");
}
}
VCOLDataWrapper::VCOLDataWrapper(COLLADAFW::MeshVertexData &vdata) : mVData(&vdata) {}
template<typename T>
static void colladaAddColor(T values, MLoopCol *mloopcol, int v_index, int stride)
{
if (values->empty() || values->getCount() < (v_index + 1) * stride) {
fprintf(stderr,
"VCOLDataWrapper.getvcol(): Out of Bounds error: index %d points outside value "
"list of length %zd (with stride=%d) \n",
v_index,
values->getCount(),
stride);
return;
}
mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]);
mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]);
mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]);
if (stride == 4) {
mloopcol->a = unit_float_to_uchar_clamp((*values)[v_index * stride + 3]);
}
}
void VCOLDataWrapper::get_vcol(int v_index, MLoopCol *mloopcol)
{
int stride = mVData->getStride(0);
if (stride == 0) {
2014-05-08 04:53:05 +10:00
stride = 3;
}
switch (mVData->getType()) {
case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: {
COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues();
colladaAddColor<COLLADAFW::ArrayPrimitiveType<float> *>(values, mloopcol, v_index, stride);
break;
}
case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: {
COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues();
colladaAddColor<COLLADAFW::ArrayPrimitiveType<double> *>(values, mloopcol, v_index, stride);
break;
}
default:
fprintf(stderr, "VCOLDataWrapper.getvcol(): unknown data type\n");
}
}
MeshImporter::MeshImporter(UnitConverter *unitconv,
bool use_custom_normals,
ArmatureImporter *arm,
Main *bmain,
Scene *sce,
ViewLayer *view_layer)
2018-06-08 08:10:35 +02:00
: unitconverter(unitconv),
use_custom_normals(use_custom_normals),
m_bmain(bmain),
scene(sce),
view_layer(view_layer),
2019-04-16 16:40:47 +02:00
armature_importer(arm)
{
/* pass */
}
bool MeshImporter::set_poly_indices(int *face_verts,
Mesh: Replace MPoly struct with offset indices Implements #95967. Currently the `MPoly` struct is 12 bytes, and stores the index of a face's first corner and the number of corners/verts/edges. Polygons and corners are always created in order by Blender, meaning each face's corners will be after the previous face's corners. We can take advantage of this fact and eliminate the redundancy in mesh face storage by only storing a single integer corner offset for each face. The size of the face is then encoded by the offset of the next face. The size of a single integer is 4 bytes, so this reduces memory usage by 3 times. The same method is used for `CurvesGeometry`, so Blender already has an abstraction to simplify using these offsets called `OffsetIndices`. This class is used to easily retrieve a range of corner indices for each face. This also gives the opportunity for sharing some logic with curves. Another benefit of the change is that the offsets and sizes stored in `MPoly` can no longer disagree with each other. Storing faces in the order of their corners can simplify some code too. Face/polygon variables now use the `IndexRange` type, which comes with quite a few utilities that can simplify code. Some: - The offset integer array has to be one longer than the face count to avoid a branch for every face, which means the data is no longer part of the mesh's `CustomData`. - We lose the ability to "reference" an original mesh's offset array until more reusable CoW from #104478 is committed. That will be added in a separate commit. - Since they aren't part of `CustomData`, poly offsets often have to be copied manually. - To simplify using `OffsetIndices` in many places, some functions and structs in headers were moved to only compile in C++. - All meshes created by Blender use the same order for faces and face corners, but just in case, meshes with mismatched order are fixed by versioning code. - `MeshPolygon.totloop` is no longer editable in RNA. This API break is necessary here unfortunately. It should be worth it in 3.6, since that's the best way to allow loading meshes from 4.0, which is important for an LTS version. Pull Request: https://projects.blender.org/blender/blender/pulls/105938
2023-04-04 20:39:28 +02:00
int loop_index,
const uint *indices,
int loop_count)
{
bool broken_loop = false;
for (int index = 0; index < loop_count; index++) {
/* Test if loop defines a hole */
if (!broken_loop) {
for (int i = 0; i < index; i++) {
if (indices[i] == indices[index]) {
/* duplicate index -> not good */
broken_loop = true;
}
}
}
*face_verts = indices[index];
face_verts++;
}
return broken_loop;
}
void MeshImporter::set_vcol(MLoopCol *mloopcol,
VCOLDataWrapper &vob,
int loop_index,
COLLADAFW::IndexList &index_list,
int count)
{
int index;
for (index = 0; index < count; index++, mloopcol++) {
int v_index = index_list.getIndex(index + loop_index);
vob.get_vcol(v_index, mloopcol);
}
}
Mesh: Move UV layers to generic attributes Currently the `MLoopUV` struct stores UV coordinates and flags related to editing UV maps in the UV editor. This patch changes the coordinates to use the generic 2D vector type, and moves the flags into three separate boolean attributes. This follows the design in T95965, with the ultimate intention of simplifying code and improving performance. Importantly, the change allows exporters and renderers to use UVs "touched" by geometry nodes, which only creates generic attributes. It also allows geometry nodes to create "proper" UV maps from scratch, though only with the Store Named Attribute node for now. The new design considers any 2D vector attribute on the corner domain to be a UV map. In the future, they might be distinguished from regular 2D vectors with attribute metadata, which may be helpful because they are often interpolated differently. Most of the code changes deal with passing around UV BMesh custom data offsets and tracking the boolean "sublayers". The boolean layers are use the following prefixes for attribute names: vert selection: `.vs.`, edge selection: `.es.`, pinning: `.pn.`. Currently these are short to avoid using up the maximum length of attribute names. To accommodate for these 4 extra characters, the name length limit is enlarged to 68 bytes, while the maximum user settable name length is still 64 bytes. Unfortunately Python/RNA API access to the UV flag data becomes slower. Accessing the boolean layers directly is be better for performance in general. Like the other mesh SoA refactors, backward and forward compatibility aren't affected, and won't be changed until 4.0. We pay for that by making mesh reading and writing more expensive with conversions. Resolves T85962 Differential Revision: https://developer.blender.org/D14365
2023-01-10 00:47:04 -05:00
void MeshImporter::set_face_uv(blender::float2 *mloopuv,
UVDataWrapper &uvs,
int start_index,
COLLADAFW::IndexList &index_list,
int count)
{
/* per face vertex indices, this means for quad we have 4 indices, not 8 */
COLLADAFW::UIntValuesArray &indices = index_list.getIndices();
for (int index = 0; index < count; index++) {
int uv_index = indices[index + start_index];
Mesh: Move UV layers to generic attributes Currently the `MLoopUV` struct stores UV coordinates and flags related to editing UV maps in the UV editor. This patch changes the coordinates to use the generic 2D vector type, and moves the flags into three separate boolean attributes. This follows the design in T95965, with the ultimate intention of simplifying code and improving performance. Importantly, the change allows exporters and renderers to use UVs "touched" by geometry nodes, which only creates generic attributes. It also allows geometry nodes to create "proper" UV maps from scratch, though only with the Store Named Attribute node for now. The new design considers any 2D vector attribute on the corner domain to be a UV map. In the future, they might be distinguished from regular 2D vectors with attribute metadata, which may be helpful because they are often interpolated differently. Most of the code changes deal with passing around UV BMesh custom data offsets and tracking the boolean "sublayers". The boolean layers are use the following prefixes for attribute names: vert selection: `.vs.`, edge selection: `.es.`, pinning: `.pn.`. Currently these are short to avoid using up the maximum length of attribute names. To accommodate for these 4 extra characters, the name length limit is enlarged to 68 bytes, while the maximum user settable name length is still 64 bytes. Unfortunately Python/RNA API access to the UV flag data becomes slower. Accessing the boolean layers directly is be better for performance in general. Like the other mesh SoA refactors, backward and forward compatibility aren't affected, and won't be changed until 4.0. We pay for that by making mesh reading and writing more expensive with conversions. Resolves T85962 Differential Revision: https://developer.blender.org/D14365
2023-01-10 00:47:04 -05:00
uvs.getUV(uv_index, mloopuv[index]);
}
}
#ifdef COLLADA_DEBUG
void MeshImporter::print_index_list(COLLADAFW::IndexList &index_list)
{
fprintf(stderr, "Index list for \"%s\":\n", index_list.getName().c_str());
for (int i = 0; i < index_list.getIndicesCount(); i += 2) {
fprintf(stderr, "%u, %u\n", index_list.getIndex(i), index_list.getIndex(i + 1));
}
fprintf(stderr, "\n");
}
#endif
bool MeshImporter::is_nice_mesh(COLLADAFW::Mesh *mesh)
{
COLLADAFW::MeshPrimitiveArray &prim_arr = mesh->getMeshPrimitives();
const std::string &name = bc_get_dae_name(mesh);
for (uint i = 0; i < prim_arr.getCount(); i++) {
COLLADAFW::MeshPrimitive *mp = prim_arr[i];
COLLADAFW::MeshPrimitive::PrimitiveType type = mp->getPrimitiveType();
const char *type_str = bc_primTypeToStr(type);
2023-05-24 11:21:18 +10:00
/* OpenCollada passes POLYGONS type for `<polylist>`. */
if (ELEM(type, COLLADAFW::MeshPrimitive::POLYLIST, COLLADAFW::MeshPrimitive::POLYGONS)) {
2012-06-12 22:05:33 +00:00
COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp;
COLLADAFW::Polygons::VertexCountArray &vca = mpvc->getGroupedVerticesVertexCountArray();
int hole_count = 0;
int nonface_count = 0;
for (uint j = 0; j < vca.getCount(); j++) {
int count = vca[j];
if (abs(count) < 3) {
nonface_count++;
}
if (count < 0) {
hole_count++;
}
}
if (hole_count > 0) {
fprintf(stderr,
"WARNING: Primitive %s in %s: %d holes not imported (unsupported)\n",
type_str,
name.c_str(),
hole_count);
}
if (nonface_count > 0) {
fprintf(stderr,
"WARNING: Primitive %s in %s: %d faces with vertex count < 3 (rejected)\n",
type_str,
name.c_str(),
nonface_count);
}
}
2012-06-27 18:29:47 +00:00
else if (type == COLLADAFW::MeshPrimitive::LINES) {
/* TODO: Add Checker for line syntax here */
}
else if (!ELEM(type,
COLLADAFW::MeshPrimitive::TRIANGLES,
COLLADAFW::MeshPrimitive::TRIANGLE_FANS))
{
fprintf(stderr, "ERROR: Primitive type %s is not supported.\n", type_str);
return false;
}
}
return true;
}
void MeshImporter::read_vertices(COLLADAFW::Mesh *mesh, Mesh *blender_mesh)
{
/* vertices */
COLLADAFW::MeshVertexData &pos = mesh->getPositions();
if (pos.empty()) {
return;
}
int stride = pos.getStride(0);
if (stride == 0) {
2012-06-12 22:05:33 +00:00
stride = 3;
}
blender_mesh->verts_num = pos.getFloatValues()->getCount() / stride;
CustomData_add_layer_named(
&blender_mesh->vert_data, CD_PROP_FLOAT3, CD_CONSTRUCT, blender_mesh->verts_num, "position");
MutableSpan<float3> positions = blender_mesh->vert_positions_for_write();
Mesh: Move positions to a generic attribute **Changes** As described in T93602, this patch removes all use of the `MVert` struct, replacing it with a generic named attribute with the name `"position"`, consistent with other geometry types. Variable names have been changed from `verts` to `positions`, to align with the attribute name and the more generic design (positions are not vertices, they are just an attribute stored on the point domain). This change is made possible by previous commits that moved all other data out of `MVert` to runtime data or other generic attributes. What remains is mostly a simple type change. Though, the type still shows up 859 times, so the patch is quite large. One compromise is that now `CD_MASK_BAREMESH` now contains `CD_PROP_FLOAT3`. With the general move towards generic attributes over custom data types, we are removing use of these type masks anyway. **Benefits** The most obvious benefit is reduced memory usage and the benefits that brings in memory-bound situations. `float3` is only 3 bytes, in comparison to `MVert` which was 4. When there are millions of vertices this starts to matter more. The other benefits come from using a more generic type. Instead of writing algorithms specifically for `MVert`, code can just use arrays of vectors. This will allow eliminating many temporary arrays or wrappers used to extract positions. Many possible improvements aren't implemented in this patch, though I did switch simplify or remove the process of creating temporary position arrays in a few places. The design clarity that "positions are just another attribute" brings allows removing explicit copying of vertices in some procedural operations-- they are just processed like most other attributes. **Performance** This touches so many areas that it's hard to benchmark exhaustively, but I observed some areas as examples. * The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster. * The Spring splash screen went from ~4.3 to ~4.5 fps. * The subdivision surface modifier/node was slightly faster RNA access through Python may be slightly slower, since now we need a name lookup instead of just a custom data type lookup for each index. **Future Improvements** * Remove uses of "vert_coords" functions: * `BKE_mesh_vert_coords_alloc` * `BKE_mesh_vert_coords_get` * `BKE_mesh_vert_coords_apply{_with_mat4}` * Remove more hidden copying of positions * General simplification now possible in many areas * Convert more code to C++ to use `float3` instead of `float[3]` * Currently `reinterpret_cast` is used for those C-API functions Differential Revision: https://developer.blender.org/D15982
2023-01-10 00:10:43 -05:00
for (const int i : positions.index_range()) {
get_vector(positions[i], pos, i, stride);
}
}
bool MeshImporter::primitive_has_useable_normals(COLLADAFW::MeshPrimitive *mp)
{
bool has_useable_normals = false;
int normals_count = mp->getNormalIndices().getCount();
if (normals_count > 0) {
int index_count = mp->getPositionIndices().getCount();
if (index_count == normals_count) {
has_useable_normals = true;
}
else {
fprintf(stderr,
2012-06-12 22:05:33 +00:00
"Warning: Number of normals %d is different from the number of vertices %d, "
"skipping normals\n",
normals_count,
index_count);
}
}
return has_useable_normals;
}
bool MeshImporter::primitive_has_faces(COLLADAFW::MeshPrimitive *mp)
{
bool has_faces = false;
int type = mp->getPrimitiveType();
switch (type) {
case COLLADAFW::MeshPrimitive::TRIANGLES:
case COLLADAFW::MeshPrimitive::TRIANGLE_FANS:
case COLLADAFW::MeshPrimitive::POLYLIST:
case COLLADAFW::MeshPrimitive::POLYGONS: {
has_faces = true;
break;
}
default: {
has_faces = false;
break;
}
}
return has_faces;
}
static std::string extract_vcolname(const COLLADAFW::String &collada_id)
{
std::string colname = collada_id;
int spos = colname.find("-mesh-colors-");
if (spos != std::string::npos) {
colname = colname.substr(spos + 13);
}
return colname;
}
void MeshImporter::allocate_poly_data(COLLADAFW::Mesh *collada_mesh, Mesh *mesh)
2012-08-11 22:12:32 +00:00
{
COLLADAFW::MeshPrimitiveArray &prim_arr = collada_mesh->getMeshPrimitives();
int total_poly_count = 0;
int total_loop_count = 0;
/* collect edge_count and face_count from all parts */
for (int i = 0; i < prim_arr.getCount(); i++) {
COLLADAFW::MeshPrimitive *mp = prim_arr[i];
int type = mp->getPrimitiveType();
switch (type) {
case COLLADAFW::MeshPrimitive::TRIANGLES:
case COLLADAFW::MeshPrimitive::TRIANGLE_FANS:
case COLLADAFW::MeshPrimitive::POLYLIST:
2013-07-19 10:40:43 +00:00
case COLLADAFW::MeshPrimitive::POLYGONS: {
COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp;
size_t prim_poly_count = mpvc->getFaceCount();
size_t prim_loop_count = 0;
for (int index = 0; index < prim_poly_count; index++) {
int vcount = get_vertex_count(mpvc, index);
if (vcount > 0) {
prim_loop_count += vcount;
total_poly_count++;
}
else {
/* TODO: this is a hole and not another polygon! */
}
}
total_loop_count += prim_loop_count;
break;
}
2013-07-19 10:40:43 +00:00
default:
break;
}
}
/* Add the data containers */
if (total_poly_count > 0) {
mesh->faces_num = total_poly_count;
mesh->corners_num = total_loop_count;
BKE_mesh_face_offsets_ensure_alloc(mesh);
Mesh: Replace MLoop struct with generic attributes Implements #102359. Split the `MLoop` struct into two separate integer arrays called `corner_verts` and `corner_edges`, referring to the vertex each corner is attached to and the next edge around the face at each corner. These arrays can be sliced to give access to the edges or vertices in a face. Then they are often referred to as "poly_verts" or "poly_edges". The main benefits are halving the necessary memory bandwidth when only one array is used and simplifications from using regular integer indices instead of a special-purpose struct. The commit also starts a renaming from "loop" to "corner" in mesh code. Like the other mesh struct of array refactors, forward compatibility is kept by writing files with the older format. This will be done until 4.0 to ease the transition process. Looking at a small portion of the patch should give a good impression for the rest of the changes. I tried to make the changes as small as possible so it's easy to tell the correctness from the diff. Though I found Blender developers have been very inventive over the last decade when finding different ways to loop over the corners in a face. For performance, nearly every piece of code that deals with `Mesh` is slightly impacted. Any algorithm that is memory bottle-necked should see an improvement. For example, here is a comparison of interpolating a vertex float attribute to face corners (Ryzen 3700x): **Before** (Average: 3.7 ms, Min: 3.4 ms) ``` threading::parallel_for(loops.index_range(), 4096, [&](IndexRange range) { for (const int64_t i : range) { dst[i] = src[loops[i].v]; } }); ``` **After** (Average: 2.9 ms, Min: 2.6 ms) ``` array_utils::gather(src, corner_verts, dst); ``` That's an improvement of 28% to the average timings, and it's also a simplification, since an index-based routine can be used instead. For more examples using the new arrays, see the design task. Pull Request: https://projects.blender.org/blender/blender/pulls/104424
2023-03-20 15:55:13 +01:00
CustomData_add_layer_named(
&mesh->corner_data, CD_PROP_INT32, CD_SET_DEFAULT, mesh->corners_num, ".corner_vert");
uint totuvset = collada_mesh->getUVCoords().getInputInfosArray().getCount();
for (int i = 0; i < totuvset; i++) {
if (collada_mesh->getUVCoords().getLength(i) == 0) {
totuvset = 0;
break;
}
}
if (totuvset > 0) {
for (int i = 0; i < totuvset; i++) {
COLLADAFW::MeshVertexData::InputInfos *info =
collada_mesh->getUVCoords().getInputInfosArray()[i];
COLLADAFW::String &uvname = info->mName;
/* Allocate space for UV_data */
CustomData_add_layer_named(
&mesh->corner_data, CD_PROP_FLOAT2, CD_SET_DEFAULT, mesh->corners_num, uvname);
}
/* activate the first uv map */
CustomData_set_layer_active(&mesh->corner_data, CD_PROP_FLOAT2, 0);
}
int totcolset = collada_mesh->getColors().getInputInfosArray().getCount();
if (totcolset > 0) {
for (int i = 0; i < totcolset; i++) {
COLLADAFW::MeshVertexData::InputInfos *info =
collada_mesh->getColors().getInputInfosArray()[i];
COLLADAFW::String colname = extract_vcolname(info->mName);
CustomData_add_layer_named(
&mesh->corner_data, CD_PROP_BYTE_COLOR, CD_SET_DEFAULT, mesh->corners_num, colname);
}
BKE_id_attributes_active_color_set(
&mesh->id, CustomData_get_layer_name(&mesh->corner_data, CD_PROP_BYTE_COLOR, 0));
BKE_id_attributes_default_color_set(
&mesh->id, CustomData_get_layer_name(&mesh->corner_data, CD_PROP_BYTE_COLOR, 0));
}
}
}
uint MeshImporter::get_vertex_count(COLLADAFW::Polygons *mp, int index)
{
int type = mp->getPrimitiveType();
int result;
switch (type) {
case COLLADAFW::MeshPrimitive::TRIANGLES:
2013-07-19 10:40:43 +00:00
case COLLADAFW::MeshPrimitive::TRIANGLE_FANS: {
result = 3;
break;
}
case COLLADAFW::MeshPrimitive::POLYLIST:
2013-07-19 10:40:43 +00:00
case COLLADAFW::MeshPrimitive::POLYGONS: {
result = mp->getGroupedVerticesVertexCountArray()[index];
break;
}
2013-07-19 10:40:43 +00:00
default: {
result = -1;
break;
}
}
return result;
}
uint MeshImporter::get_loose_edge_count(COLLADAFW::Mesh *mesh)
{
COLLADAFW::MeshPrimitiveArray &prim_arr = mesh->getMeshPrimitives();
int loose_edge_count = 0;
/* collect edge_count and face_count from all parts */
for (int i = 0; i < prim_arr.getCount(); i++) {
COLLADAFW::MeshPrimitive *mp = prim_arr[i];
int type = mp->getPrimitiveType();
switch (type) {
2013-07-19 10:40:43 +00:00
case COLLADAFW::MeshPrimitive::LINES: {
size_t prim_totface = mp->getFaceCount();
loose_edge_count += prim_totface;
break;
}
2013-07-19 10:40:43 +00:00
default:
break;
}
}
return loose_edge_count;
}
void MeshImporter::mesh_add_edges(Mesh *mesh, int len)
{
CustomData edge_data;
int totedge;
if (len == 0) {
return;
}
totedge = mesh->edges_num + len;
/* Update custom-data. */
CustomData_init_layout_from(
&mesh->edge_data, &edge_data, CD_MASK_MESH.emask, CD_SET_DEFAULT, totedge);
CustomData_copy_data(&mesh->edge_data, &edge_data, 0, 0, mesh->edges_num);
if (!CustomData_has_layer_named(&edge_data, CD_PROP_INT32_2D, ".edge_verts")) {
CustomData_add_layer_named(&edge_data, CD_PROP_INT32_2D, CD_CONSTRUCT, totedge, ".edge_verts");
}
CustomData_free(&mesh->edge_data);
mesh->edge_data = edge_data;
BKE_mesh_runtime_clear_cache(mesh);
mesh->edges_num = totedge;
}
void MeshImporter::read_lines(COLLADAFW::Mesh *mesh, Mesh *blender_mesh)
{
uint loose_edge_count = get_loose_edge_count(mesh);
2012-06-12 22:05:33 +00:00
if (loose_edge_count > 0) {
uint face_edge_count = blender_mesh->edges_num;
// uint total_edge_count = loose_edge_count + face_edge_count; /* UNUSED. */
mesh_add_edges(blender_mesh, loose_edge_count);
MutableSpan<blender::int2> edges = blender_mesh->edges_for_write();
Mesh: Move edges to a generic attribute Implements #95966, as the final step of #95965. This commit changes the storage of mesh edge vertex indices from the `MEdge` type to the generic `int2` attribute type. This follows the general design for geometry and the attribute system, where the data storage type and the usage semantics are separated. The main benefit of the change is reduced memory usage-- the requirements of storing mesh edges is reduced by 1/3. For example, this saves 8MB on a 1 million vertex grid. This also gives performance benefits to any memory-bound mesh processing algorithm that uses edges. Another benefit is that all of the edge's vertex indices are contiguous. In a few cases, it's helpful to process all of them as `Span<int>` rather than `Span<int2>`. Similarly, the type is more likely to match a generic format used by a library, or code that shouldn't know about specific Blender `Mesh` types. Various Notes: - The `.edge_verts` name is used to reflect a mapping between domains, similar to `.corner_verts`, etc. The period means that it the data shouldn't change arbitrarily by the user or procedural operations. - `edge[0]` is now used instead of `edge.v1` - Signed integers are used instead of unsigned to reduce the mixing of signed-ness, which can be error prone. - All of the previously used core mesh data types (`MVert`, `MEdge`, `MLoop`, `MPoly` are now deprecated. Only generic types are used). - The `vec2i` DNA type is used in the few C files where necessary. Pull Request: https://projects.blender.org/blender/blender/pulls/106638
2023-04-17 13:47:41 +02:00
blender::int2 *edge = edges.data() + face_edge_count;
COLLADAFW::MeshPrimitiveArray &prim_arr = mesh->getMeshPrimitives();
Partial rewrite of the Collada Module for Blender 2.8 Most important changes are in the Animation exporter and Animation Importer. There is still some cleaning up to be done. But the Exporter/Importer basically work within Blender 2.8 Some details: User Interface: The interface has been reorganized to look more like the FBX interface. New options in user interface: * keep_keyframes: When sampling the distance between 2 keyframes is defined by the sampling rate. Furthermore the keyframes defined in the FCurves are not exported. However when this option is enabled then also the defined keyframes will be added to the exported fcurves * keep_smooth_curves: When sampling we do not use FCurves. So we also have no Curve handles for smooth exporting. However when this option is enabled, Blender does its best to recreate the handles for export. This is a very experimental feature and it is know to break when: - the exported animated objects have parent inverse matrices different from the unit matrix - The exported objects have negative scaling There may be many other situations when this feature breaks. This needs to be further tested. It may be removed later or replaced by something less wonky. BlenderContext: is a new class that contains the bridge to Blender. It contains pointers to the current export/import context plus derived values of Depsgraph, Scene, Main Reporting: I reorganized the output on the Blender Console to become more informative and more readable Preservation of Item names: name attributes are now encoded with XML entities. This makes sure that i can export/import names exactly defined in the tool. This affects material names, bone names and object names. Hierarchy export: * Object and Bone Hierarchies are now exported correctly by taking the Blender parent/child hierarchy into account * Export also not selected intermediate objects Problem: When we export an Object Hierarchy, then we must export all elements of the hierarchy to maintain the transforms. This is especially important when exporting animated objects, because the animation curves are exported as relative curves based on the parent-child hierarchy. If an intermediate animated object is missing then the exported animation breaks. Solution: If the "Selected" Optioon is enabled, then take care to also export all objects which are not selected and hidden, but which are parents of selected objects. Node Based Material Importer (wip): Added basic support for Materials with diffuse color and diffuse textures. More properties (opacity, emission) need changes in the used shader. Note: Materials are all constructed by using the principled BSDF shader. Animation Exporter: * Massive optimization of the Animation Bake tool (Animation Sampler). Instead of sampling each fcurve separately, i now sample all exported fcurves simultaneously. So i avoid many (many!) scene updates during animation export. * Add support for Continuous Acceleration (Fcurve handles) This allows us to create smoother FCurves during importing Collada Animation curves. Possibly this should become an option ionstead of a fixed import feature. * Add support for sampling curves (to bake animations) * The animation sampler now can be used for any animation curve. Before the sampler only looked at curves which are supported by Standard Collada 1.4. However the Collada exporter currently ignores all animation curves which are not covered by the 1.4.1 Collada Standards. There is still some room for improvements here (work in progres) Known issues: * Some exports do currently not work reliably, among those are the camera animations, material animations and light animations those animations will be added back next (work in progres) * Exporting animation curves with keyframes (and tangents) sometimes results in odd curves (when parent inverse matrix is involved) This needs to be checked in more depth (probably it can not be solved). * Export of "all animations in scene" is disabled because the Collada Importer can not handle this reliably at the moment (work in progres). * Support for Animation Clip export Added one extra level to the exported animations such that now all scene animations are enclosed: <Animation name="id_name(ob)_Action"> <Animation>...</Animation> ... </Animation> Animation Importer: * Import of animations for objects with multiple materials When importing multiple materials for one object, the imported material animation curves have all been assigned to the first material in the object. Error handling (wip): The Importer was a bit confused as it sometimes ignored fatal parsing errors and continued to import. I did my best to unconfuse it, but i believe that this needs to be tested more. Refactoring: update : move generation of effect id names into own function update : adjust importer/exporter for no longer supported HEMI lights cleanup: Removed no lopnger existing attribute from the exporter presets cleanup: Removed not needed Context attribute from DocumentExporter fix : Avoid duplicate deletion of temporary items cleanup: fixed indentation and white space issues update : Make BCAnimation class more self contained cleanup: Renamed classes, updated comments for better reading cleanup: Moved static class functions to collada_utils cleanup: Moved typedefs to more intuitive locations cleanup: indentation and class method declarations cleanup: Removed no longer needed methods update : Moved Classes into separate files cleanup: Added comments cleanup: take care of name conventions ... : many more small changes, not helpful to list them all
2018-11-23 15:57:45 +01:00
for (int index = 0; index < prim_arr.getCount(); index++) {
COLLADAFW::MeshPrimitive *mp = prim_arr[index];
int type = mp->getPrimitiveType();
2012-06-27 18:29:47 +00:00
if (type == COLLADAFW::MeshPrimitive::LINES) {
uint edge_count = mp->getFaceCount();
uint *indices = mp->getPositionIndices().getData();
for (int j = 0; j < edge_count; j++, edge++) {
Mesh: Move edges to a generic attribute Implements #95966, as the final step of #95965. This commit changes the storage of mesh edge vertex indices from the `MEdge` type to the generic `int2` attribute type. This follows the general design for geometry and the attribute system, where the data storage type and the usage semantics are separated. The main benefit of the change is reduced memory usage-- the requirements of storing mesh edges is reduced by 1/3. For example, this saves 8MB on a 1 million vertex grid. This also gives performance benefits to any memory-bound mesh processing algorithm that uses edges. Another benefit is that all of the edge's vertex indices are contiguous. In a few cases, it's helpful to process all of them as `Span<int>` rather than `Span<int2>`. Similarly, the type is more likely to match a generic format used by a library, or code that shouldn't know about specific Blender `Mesh` types. Various Notes: - The `.edge_verts` name is used to reflect a mapping between domains, similar to `.corner_verts`, etc. The period means that it the data shouldn't change arbitrarily by the user or procedural operations. - `edge[0]` is now used instead of `edge.v1` - Signed integers are used instead of unsigned to reduce the mixing of signed-ness, which can be error prone. - All of the previously used core mesh data types (`MVert`, `MEdge`, `MLoop`, `MPoly` are now deprecated. Only generic types are used). - The `vec2i` DNA type is used in the few C files where necessary. Pull Request: https://projects.blender.org/blender/blender/pulls/106638
2023-04-17 13:47:41 +02:00
(*edge)[0] = indices[2 * j];
(*edge)[1] = indices[2 * j + 1];
}
}
}
}
}
void MeshImporter::read_polys(COLLADAFW::Mesh *collada_mesh,
Mesh *mesh,
blender::Vector<blender::float3> &loop_normals)
{
using namespace blender;
uint i;
allocate_poly_data(collada_mesh, mesh);
UVDataWrapper uvs(collada_mesh->getUVCoords());
VCOLDataWrapper vcol(collada_mesh->getColors());
MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
int face_index = 0;
int loop_index = 0;
MaterialIdPrimitiveArrayMap mat_prim_map;
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter material_indices = attributes.lookup_or_add_for_write_span<int>(
"material_index", bke::AttrDomain::Face);
bke::SpanAttributeWriter sharp_faces = attributes.lookup_or_add_for_write_span<bool>(
"sharp_face", bke::AttrDomain::Face);
COLLADAFW::MeshPrimitiveArray &prim_arr = collada_mesh->getMeshPrimitives();
COLLADAFW::MeshVertexData &nor = collada_mesh->getNormals();
for (i = 0; i < prim_arr.getCount(); i++) {
COLLADAFW::MeshPrimitive *mp = prim_arr[i];
/* faces */
size_t prim_faces_num = mp->getFaceCount();
uint *position_indices = mp->getPositionIndices().getData();
uint *normal_indices = mp->getNormalIndices().getData();
bool mp_has_normals = primitive_has_useable_normals(mp);
bool mp_has_faces = primitive_has_faces(mp);
int collada_meshtype = mp->getPrimitiveType();
if (collada_meshtype == COLLADAFW::MeshPrimitive::LINES) {
continue; /* read the lines later after all the rest is done */
}
/* Since we cannot set `poly->mat_nr` here, we store a portion of `mesh->mpoly` in Primitive.
*/
Primitive prim = {face_index, &material_indices.span[face_index], 0};
/* If MeshPrimitive is TRIANGLE_FANS we split it into triangles
2020-10-14 14:43:54 +11:00
* The first triangle-fan vertex will be the first vertex in every triangle
* XXX The proper function of TRIANGLE_FANS is not tested!!!
* XXX In particular the handling of the normal_indices is very wrong */
/* TODO: UV, vertex color and custom normal support */
if (collada_meshtype == COLLADAFW::MeshPrimitive::TRIANGLE_FANS) {
uint grouped_vertex_count = mp->getGroupedVertexElementsCount();
for (uint group_index = 0; group_index < grouped_vertex_count; group_index++) {
2023-01-16 13:57:10 +11:00
uint first_vertex = position_indices[0]; /* Store first triangle-fan vertex. */
uint first_normal = normal_indices[0]; /* Store first triangle-fan vertex normal. */
uint vertex_count = mp->getGroupedVerticesVertexCount(group_index);
for (uint vertex_index = 0; vertex_index < vertex_count - 2; vertex_index++) {
/* For each triangle store indices of its 3 vertices */
uint triangle_vertex_indices[3] = {
first_vertex, position_indices[1], position_indices[2]};
face_offsets[face_index] = loop_index;
Mesh: Replace MPoly struct with offset indices Implements #95967. Currently the `MPoly` struct is 12 bytes, and stores the index of a face's first corner and the number of corners/verts/edges. Polygons and corners are always created in order by Blender, meaning each face's corners will be after the previous face's corners. We can take advantage of this fact and eliminate the redundancy in mesh face storage by only storing a single integer corner offset for each face. The size of the face is then encoded by the offset of the next face. The size of a single integer is 4 bytes, so this reduces memory usage by 3 times. The same method is used for `CurvesGeometry`, so Blender already has an abstraction to simplify using these offsets called `OffsetIndices`. This class is used to easily retrieve a range of corner indices for each face. This also gives the opportunity for sharing some logic with curves. Another benefit of the change is that the offsets and sizes stored in `MPoly` can no longer disagree with each other. Storing faces in the order of their corners can simplify some code too. Face/polygon variables now use the `IndexRange` type, which comes with quite a few utilities that can simplify code. Some: - The offset integer array has to be one longer than the face count to avoid a branch for every face, which means the data is no longer part of the mesh's `CustomData`. - We lose the ability to "reference" an original mesh's offset array until more reusable CoW from #104478 is committed. That will be added in a separate commit. - Since they aren't part of `CustomData`, poly offsets often have to be copied manually. - To simplify using `OffsetIndices` in many places, some functions and structs in headers were moved to only compile in C++. - All meshes created by Blender use the same order for faces and face corners, but just in case, meshes with mismatched order are fixed by versioning code. - `MeshPolygon.totloop` is no longer editable in RNA. This API break is necessary here unfortunately. It should be worth it in 3.6, since that's the best way to allow loading meshes from 4.0, which is important for an LTS version. Pull Request: https://projects.blender.org/blender/blender/pulls/105938
2023-04-04 20:39:28 +02:00
set_poly_indices(&corner_verts[loop_index], loop_index, triangle_vertex_indices, 3);
if (mp_has_normals) { /* vertex normals, same implementation as for the triangles */
2021-02-05 16:23:34 +11:00
/* The same for vertices normals. */
uint vertex_normal_indices[3] = {first_normal, normal_indices[1], normal_indices[2]};
sharp_faces.span[face_index] = is_flat_face(vertex_normal_indices, nor, 3);
normal_indices++;
}
face_index++;
loop_index += 3;
prim.faces_num++;
2012-06-12 22:05:33 +00:00
}
/* Moving cursor to the next triangle fan. */
if (mp_has_normals) {
normal_indices += 2;
}
position_indices += 2;
}
}
if (ELEM(collada_meshtype,
COLLADAFW::MeshPrimitive::POLYLIST,
COLLADAFW::MeshPrimitive::POLYGONS,
COLLADAFW::MeshPrimitive::TRIANGLES))
{
COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp;
uint start_index = 0;
COLLADAFW::IndexListArray &index_list_array_uvcoord = mp->getUVCoordIndicesArray();
COLLADAFW::IndexListArray &index_list_array_vcolor = mp->getColorIndicesArray();
int invalid_loop_holes = 0;
for (uint j = 0; j < prim_faces_num; j++) {
/* Vertices in polygon: */
int vcount = get_vertex_count(mpvc, j);
if (vcount < 0) {
continue; /* TODO: add support for holes */
}
face_offsets[face_index] = loop_index;
bool broken_loop = set_poly_indices(
Mesh: Replace MPoly struct with offset indices Implements #95967. Currently the `MPoly` struct is 12 bytes, and stores the index of a face's first corner and the number of corners/verts/edges. Polygons and corners are always created in order by Blender, meaning each face's corners will be after the previous face's corners. We can take advantage of this fact and eliminate the redundancy in mesh face storage by only storing a single integer corner offset for each face. The size of the face is then encoded by the offset of the next face. The size of a single integer is 4 bytes, so this reduces memory usage by 3 times. The same method is used for `CurvesGeometry`, so Blender already has an abstraction to simplify using these offsets called `OffsetIndices`. This class is used to easily retrieve a range of corner indices for each face. This also gives the opportunity for sharing some logic with curves. Another benefit of the change is that the offsets and sizes stored in `MPoly` can no longer disagree with each other. Storing faces in the order of their corners can simplify some code too. Face/polygon variables now use the `IndexRange` type, which comes with quite a few utilities that can simplify code. Some: - The offset integer array has to be one longer than the face count to avoid a branch for every face, which means the data is no longer part of the mesh's `CustomData`. - We lose the ability to "reference" an original mesh's offset array until more reusable CoW from #104478 is committed. That will be added in a separate commit. - Since they aren't part of `CustomData`, poly offsets often have to be copied manually. - To simplify using `OffsetIndices` in many places, some functions and structs in headers were moved to only compile in C++. - All meshes created by Blender use the same order for faces and face corners, but just in case, meshes with mismatched order are fixed by versioning code. - `MeshPolygon.totloop` is no longer editable in RNA. This API break is necessary here unfortunately. It should be worth it in 3.6, since that's the best way to allow loading meshes from 4.0, which is important for an LTS version. Pull Request: https://projects.blender.org/blender/blender/pulls/105938
2023-04-04 20:39:28 +02:00
&corner_verts[loop_index], loop_index, position_indices, vcount);
if (broken_loop) {
invalid_loop_holes += 1;
}
for (uint uvset_index = 0; uvset_index < index_list_array_uvcoord.getCount();
uvset_index++)
{
COLLADAFW::IndexList &index_list = *index_list_array_uvcoord[uvset_index];
blender::float2 *mloopuv = static_cast<blender::float2 *>(
CustomData_get_layer_named_for_write(
&mesh->corner_data, CD_PROP_FLOAT2, index_list.getName(), mesh->corners_num));
if (mloopuv == nullptr) {
fprintf(stderr,
"Collada import: Mesh [%s] : Unknown reference to TEXCOORD [#%s].\n",
mesh->id.name,
index_list.getName().c_str());
}
else {
set_face_uv(mloopuv + loop_index,
uvs,
start_index,
*index_list_array_uvcoord[uvset_index],
vcount);
}
}
if (mp_has_normals) {
Mesh: Replace MPoly struct with offset indices Implements #95967. Currently the `MPoly` struct is 12 bytes, and stores the index of a face's first corner and the number of corners/verts/edges. Polygons and corners are always created in order by Blender, meaning each face's corners will be after the previous face's corners. We can take advantage of this fact and eliminate the redundancy in mesh face storage by only storing a single integer corner offset for each face. The size of the face is then encoded by the offset of the next face. The size of a single integer is 4 bytes, so this reduces memory usage by 3 times. The same method is used for `CurvesGeometry`, so Blender already has an abstraction to simplify using these offsets called `OffsetIndices`. This class is used to easily retrieve a range of corner indices for each face. This also gives the opportunity for sharing some logic with curves. Another benefit of the change is that the offsets and sizes stored in `MPoly` can no longer disagree with each other. Storing faces in the order of their corners can simplify some code too. Face/polygon variables now use the `IndexRange` type, which comes with quite a few utilities that can simplify code. Some: - The offset integer array has to be one longer than the face count to avoid a branch for every face, which means the data is no longer part of the mesh's `CustomData`. - We lose the ability to "reference" an original mesh's offset array until more reusable CoW from #104478 is committed. That will be added in a separate commit. - Since they aren't part of `CustomData`, poly offsets often have to be copied manually. - To simplify using `OffsetIndices` in many places, some functions and structs in headers were moved to only compile in C++. - All meshes created by Blender use the same order for faces and face corners, but just in case, meshes with mismatched order are fixed by versioning code. - `MeshPolygon.totloop` is no longer editable in RNA. This API break is necessary here unfortunately. It should be worth it in 3.6, since that's the best way to allow loading meshes from 4.0, which is important for an LTS version. Pull Request: https://projects.blender.org/blender/blender/pulls/105938
2023-04-04 20:39:28 +02:00
/* If it turns out that we have complete custom normals for each poly
* and we want to use custom normals, this will be overridden. */
sharp_faces.span[face_index] = is_flat_face(normal_indices, nor, vcount);
if (use_custom_normals) {
/* Store the custom normals for later application. */
float vert_normal[3];
uint *cur_normal = normal_indices;
for (int k = 0; k < vcount; k++, cur_normal++) {
get_vector(vert_normal, nor, *cur_normal, 3);
normalize_v3(vert_normal);
loop_normals.append(vert_normal);
}
}
}
if (mp->hasColorIndices()) {
int vcolor_count = index_list_array_vcolor.getCount();
for (uint vcolor_index = 0; vcolor_index < vcolor_count; vcolor_index++) {
COLLADAFW::IndexList &color_index_list = *mp->getColorIndices(vcolor_index);
COLLADAFW::String colname = extract_vcolname(color_index_list.getName());
MLoopCol *mloopcol = (MLoopCol *)CustomData_get_layer_named_for_write(
&mesh->corner_data, CD_PROP_BYTE_COLOR, colname, mesh->corners_num);
if (mloopcol == nullptr) {
fprintf(stderr,
"Collada import: Mesh [%s] : Unknown reference to VCOLOR [#%s].\n",
mesh->id.name,
color_index_list.getName().c_str());
}
else {
set_vcol(mloopcol + loop_index, vcol, start_index, color_index_list, vcount);
}
}
}
face_index++;
loop_index += vcount;
start_index += vcount;
prim.faces_num++;
if (mp_has_normals) {
normal_indices += vcount;
}
position_indices += vcount;
}
if (invalid_loop_holes > 0) {
fprintf(stderr,
"Collada import: Mesh [%s] : contains %d unsupported loops (holes).\n",
mesh->id.name,
invalid_loop_holes);
}
}
if (mp_has_faces) {
mat_prim_map[mp->getMaterialId()].push_back(prim);
}
}
geom_uid_mat_mapping_map[collada_mesh->getUniqueId()] = mat_prim_map;
material_indices.finish();
}
void MeshImporter::get_vector(float v[3], COLLADAFW::MeshVertexData &arr, int i, int stride)
{
i *= stride;
switch (arr.getType()) {
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: {
COLLADAFW::ArrayPrimitiveType<float> *values = arr.getFloatValues();
if (values->empty()) {
return;
}
v[0] = (*values)[i++];
v[1] = (*values)[i++];
if (stride >= 3) {
v[2] = (*values)[i];
}
else {
v[2] = 0.0f;
}
break;
}
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: {
COLLADAFW::ArrayPrimitiveType<double> *values = arr.getDoubleValues();
if (values->empty()) {
return;
}
v[0] = float((*values)[i++]);
v[1] = float((*values)[i++]);
if (stride >= 3) {
v[2] = float((*values)[i]);
}
else {
v[2] = 0.0f;
}
break;
}
2012-06-12 22:05:33 +00:00
default:
break;
}
}
bool MeshImporter::is_flat_face(uint *nind, COLLADAFW::MeshVertexData &nor, int count)
{
float a[3], b[3];
get_vector(a, nor, *nind, 3);
normalize_v3(a);
nind++;
for (int i = 1; i < count; i++, nind++) {
get_vector(b, nor, *nind, 3);
normalize_v3(b);
float dp = dot_v3v3(a, b);
if (dp < 0.99999f || dp > 1.00001f) {
return false;
}
}
return true;
}
Object *MeshImporter::get_object_by_geom_uid(const COLLADAFW::UniqueId &geom_uid)
{
if (uid_object_map.find(geom_uid) != uid_object_map.end()) {
return uid_object_map[geom_uid];
}
return nullptr;
}
Mesh *MeshImporter::get_mesh_by_geom_uid(const COLLADAFW::UniqueId &geom_uid)
{
if (uid_mesh_map.find(geom_uid) != uid_mesh_map.end()) {
return uid_mesh_map[geom_uid];
}
return nullptr;
}
std::string *MeshImporter::get_geometry_name(const std::string &mesh_name)
{
if (this->mesh_geom_map.find(mesh_name) != this->mesh_geom_map.end()) {
return &this->mesh_geom_map[mesh_name];
}
return nullptr;
}
static bool bc_has_out_of_bound_indices(Mesh *mesh)
{
for (const int vert_i : mesh->corner_verts()) {
if (vert_i >= mesh->verts_num) {
return true;
}
}
return false;
}
/**
* this function checks if both objects have the same
* materials assigned to Object (in the same order)
* returns true if condition matches, otherwise false;
2019-03-19 15:17:46 +11:00
*/
static bool bc_has_same_material_configuration(Object *ob1, Object *ob2)
{
if (ob1->totcol != ob2->totcol) {
return false; /* not same number of materials */
}
if (ob1->totcol == 0) {
return false; /* no material at all */
}
for (int index = 0; index < ob1->totcol; index++) {
if (ob1->matbits[index] != ob2->matbits[index]) {
return false; /* shouldn't happen */
}
if (ob1->matbits[index] == 0) {
return false; /* shouldn't happen */
}
if (ob1->mat[index] != ob2->mat[index]) {
return false; /* different material assignment */
}
}
return true;
}
/**
* Caution here: This code assumes that all materials are assigned to Object
* and no material is assigned to Data.
* That is true right after the objects have been imported.
2019-03-19 15:17:46 +11:00
*/
static void bc_copy_materials_to_data(Object *ob, Mesh *mesh)
{
for (int index = 0; index < ob->totcol; index++) {
ob->matbits[index] = 0;
mesh->mat[index] = ob->mat[index];
}
}
/**
* Remove all references to materials from the object.
2019-03-19 15:17:46 +11:00
*/
static void bc_remove_materials_from_object(Object *ob, Mesh *mesh)
{
for (int index = 0; index < ob->totcol; index++) {
ob->matbits[index] = 0;
ob->mat[index] = nullptr;
}
}
std::vector<Object *> MeshImporter::get_all_users_of(Mesh *reference_mesh)
{
std::vector<Object *> mesh_users;
for (Object *ob : imported_objects) {
if (bc_is_marked(ob)) {
bc_remove_mark(ob);
Mesh *mesh = (Mesh *)ob->data;
if (mesh == reference_mesh) {
mesh_users.push_back(ob);
}
}
}
return mesh_users;
}
void MeshImporter::optimize_material_assignements()
{
for (Object *ob : imported_objects) {
Mesh *mesh = (Mesh *)ob->data;
if (ID_REAL_USERS(&mesh->id) == 1) {
bc_copy_materials_to_data(ob, mesh);
bc_remove_materials_from_object(ob, mesh);
bc_remove_mark(ob);
}
else if (ID_REAL_USERS(&mesh->id) > 1) {
bool can_move = true;
std::vector<Object *> mesh_users = get_all_users_of(mesh);
if (mesh_users.size() > 1) {
Object *ref_ob = mesh_users[0];
for (int index = 1; index < mesh_users.size(); index++) {
if (!bc_has_same_material_configuration(ref_ob, mesh_users[index])) {
can_move = false;
break;
}
}
if (can_move) {
bc_copy_materials_to_data(ref_ob, mesh);
for (Object *object : mesh_users) {
bc_remove_materials_from_object(object, mesh);
bc_remove_mark(object);
}
}
}
}
}
}
void MeshImporter::assign_material_to_geom(
COLLADAFW::MaterialBinding cmaterial,
std::map<COLLADAFW::UniqueId, Material *> &uid_material_map,
Object *ob,
const COLLADAFW::UniqueId *geom_uid,
Remove Blender Internal and legacy viewport from Blender 2.8. Brecht authored this commit, but he gave me the honours to actually do it. Here it goes; Blender Internal. Bye bye, you did great! * Point density, voxel data, ocean, environment map textures were removed, as these only worked within BI rendering. Note that the ocean modifier and the Cycles point density shader node continue to work. * Dynamic paint using material shading was removed, as this only worked with BI. If we ever wanted to support this again probably it should go through the baking API. * GPU shader export through the Python API was removed. This only worked for the old BI GLSL shaders, which no longer exists. Doing something similar for Eevee would be significantly more complicated because it uses a lot of multiplass rendering and logic outside the shader, it's probably impractical. * Collada material import / export code is mostly gone, as it only worked for BI materials. We need to add Cycles / Eevee material support at some point. * The mesh noise operator was removed since it only worked with BI material texture slots. A displacement modifier can be used instead. * The delete texture paint slot operator was removed since it only worked for BI material texture slots. Could be added back with node support. * Not all legacy viewport features are supported in the new viewport, but their code was removed. If we need to bring anything back we can look at older git revisions. * There is some legacy viewport code that I could not remove yet, and some that I probably missed. * Shader node execution code was left mostly intact, even though it is not used anywhere now. We may eventually use this to replace the texture nodes with Cycles / Eevee shader nodes. * The Cycles Bake panel now includes settings for baking multires normal and displacement maps. The underlying code needs to be merged properly, and we plan to add back support for multires AO baking and add support to Cycles baking for features like vertex color, displacement, and other missing baking features. * This commit removes DNA and the Python API for BI material, lamp, world and scene settings. This breaks a lot of addons. * There is more DNA that can be removed or renamed, where Cycles or Eevee are reusing some old BI properties but the names are not really correct anymore. * Texture slots for materials, lamps and world were removed. They remain for brushes, particles and freestyle linestyles. * 'BLENDER_RENDER' remains in the COMPAT_ENGINES of UI panels. Cycles and other renderers use this to find all panels to show, minus a few panels that they have their own replacement for.
2018-04-19 17:34:44 +02:00
short mat_index)
{
const COLLADAFW::UniqueId &ma_uid = cmaterial.getReferencedMaterial();
/* do we know this material? */
if (uid_material_map.find(ma_uid) == uid_material_map.end()) {
fprintf(stderr, "Cannot find material by UID.\n");
return;
}
/* first time we get geom_uid, ma_uid pair. Save for later check. */
materials_mapped_to_geom.insert(
std::pair<COLLADAFW::UniqueId, COLLADAFW::UniqueId>(*geom_uid, ma_uid));
Material *ma = uid_material_map[ma_uid];
/* Attention! This temporarily assigns material to object on purpose!
* See note above. */
ob->actcol = 0;
BKE_object_material_assign(m_bmain, ob, ma, mat_index + 1, BKE_MAT_ASSIGN_OBJECT);
MaterialIdPrimitiveArrayMap &mat_prim_map = geom_uid_mat_mapping_map[*geom_uid];
COLLADAFW::MaterialId mat_id = cmaterial.getMaterialId();
/* assign material indices to mesh faces */
if (mat_prim_map.find(mat_id) != mat_prim_map.end()) {
std::vector<Primitive> &prims = mat_prim_map[mat_id];
std::vector<Primitive>::iterator it;
for (it = prims.begin(); it != prims.end(); it++) {
Primitive &prim = *it;
for (int i = 0; i < prim.faces_num; i++) {
prim.material_indices[i] = mat_index;
}
}
}
}
Object *MeshImporter::create_mesh_object(
COLLADAFW::Node *node,
COLLADAFW::InstanceGeometry *geom,
2012-06-12 22:05:33 +00:00
bool isController,
Remove Blender Internal and legacy viewport from Blender 2.8. Brecht authored this commit, but he gave me the honours to actually do it. Here it goes; Blender Internal. Bye bye, you did great! * Point density, voxel data, ocean, environment map textures were removed, as these only worked within BI rendering. Note that the ocean modifier and the Cycles point density shader node continue to work. * Dynamic paint using material shading was removed, as this only worked with BI. If we ever wanted to support this again probably it should go through the baking API. * GPU shader export through the Python API was removed. This only worked for the old BI GLSL shaders, which no longer exists. Doing something similar for Eevee would be significantly more complicated because it uses a lot of multiplass rendering and logic outside the shader, it's probably impractical. * Collada material import / export code is mostly gone, as it only worked for BI materials. We need to add Cycles / Eevee material support at some point. * The mesh noise operator was removed since it only worked with BI material texture slots. A displacement modifier can be used instead. * The delete texture paint slot operator was removed since it only worked for BI material texture slots. Could be added back with node support. * Not all legacy viewport features are supported in the new viewport, but their code was removed. If we need to bring anything back we can look at older git revisions. * There is some legacy viewport code that I could not remove yet, and some that I probably missed. * Shader node execution code was left mostly intact, even though it is not used anywhere now. We may eventually use this to replace the texture nodes with Cycles / Eevee shader nodes. * The Cycles Bake panel now includes settings for baking multires normal and displacement maps. The underlying code needs to be merged properly, and we plan to add back support for multires AO baking and add support to Cycles baking for features like vertex color, displacement, and other missing baking features. * This commit removes DNA and the Python API for BI material, lamp, world and scene settings. This breaks a lot of addons. * There is more DNA that can be removed or renamed, where Cycles or Eevee are reusing some old BI properties but the names are not really correct anymore. * Texture slots for materials, lamps and world were removed. They remain for brushes, particles and freestyle linestyles. * 'BLENDER_RENDER' remains in the COMPAT_ENGINES of UI panels. Cycles and other renderers use this to find all panels to show, minus a few panels that they have their own replacement for.
2018-04-19 17:34:44 +02:00
std::map<COLLADAFW::UniqueId, Material *> &uid_material_map)
{
const COLLADAFW::UniqueId *geom_uid = &geom->getInstanciatedObjectId();
/* check if node instantiates controller or geometry */
if (isController) {
geom_uid = armature_importer->get_geometry_uid(*geom_uid);
if (!geom_uid) {
fprintf(stderr, "Couldn't find a mesh UID by controller's UID.\n");
return nullptr;
}
}
else {
if (uid_mesh_map.find(*geom_uid) == uid_mesh_map.end()) {
/* this could happen if a mesh was not created
* (e.g. if it contains unsupported geometry) */
fprintf(stderr, "Couldn't find a mesh by UID.\n");
return nullptr;
}
}
if (!uid_mesh_map[*geom_uid]) {
return nullptr;
}
/* name Object */
const std::string &id = node->getName().empty() ? node->getOriginalId() : node->getName();
const char *name = id.length() ? id.c_str() : nullptr;
/* add object */
Object *ob = bc_add_object(m_bmain, scene, view_layer, OB_MESH, name);
bc_set_mark(ob); /* used later for material assignment optimization */
/* store object pointer for ArmatureImporter */
uid_object_map[*geom_uid] = ob;
imported_objects.push_back(ob);
/* replace ob->data freeing the old one */
2012-06-12 22:05:33 +00:00
Mesh *old_mesh = (Mesh *)ob->data;
Mesh *new_mesh = uid_mesh_map[*geom_uid];
BKE_mesh_assign_object(m_bmain, ob, new_mesh);
/* Because BKE_mesh_assign_object would have already decreased it... */
id_us_plus(&old_mesh->id);
BKE_id_free_us(m_bmain, old_mesh);
COLLADAFW::MaterialBindingArray &mat_array = geom->getMaterialBindings();
/* loop through geom's materials */
for (uint i = 0; i < mat_array.getCount(); i++) {
if (mat_array[i].getReferencedMaterial().isValid()) {
assign_material_to_geom(mat_array[i], uid_material_map, ob, geom_uid, i);
}
else {
2011-03-08 20:41:43 +00:00
fprintf(stderr, "invalid referenced material for %s\n", mat_array[i].getName().c_str());
}
}
/* clean up the mesh */
BKE_mesh_validate((Mesh *)ob->data, false, false);
return ob;
}
2012-06-12 22:05:33 +00:00
bool MeshImporter::write_geometry(const COLLADAFW::Geometry *geom)
{
using namespace blender;
if (geom->getType() != COLLADAFW::Geometry::GEO_TYPE_MESH) {
/* TODO: report warning */
fprintf(stderr, "Mesh type %s is not supported\n", bc_geomTypeToStr(geom->getType()));
return true;
}
2012-06-12 22:05:33 +00:00
COLLADAFW::Mesh *mesh = (COLLADAFW::Mesh *)geom;
if (!is_nice_mesh(mesh)) {
fprintf(stderr, "Ignoring mesh %s\n", bc_get_dae_name(mesh).c_str());
return true;
}
const std::string &str_geom_id = mesh->getName().empty() ? mesh->getOriginalId() :
mesh->getName();
Mesh *blender_mesh = BKE_mesh_add(m_bmain, (char *)str_geom_id.c_str());
id_us_min(
&blender_mesh->id); /* is already 1 here, but will be set later in BKE_mesh_assign_object */
/* store the Mesh pointer to link it later with an Object
* mesh_geom_map needed to map mesh to its geometry name (for shape key naming) */
this->uid_mesh_map[mesh->getUniqueId()] = blender_mesh;
this->mesh_geom_map[std::string(blender_mesh->id.name)] = str_geom_id;
read_vertices(mesh, blender_mesh);
blender::Vector<blender::float3> loop_normals;
read_polys(mesh, blender_mesh, loop_normals);
blender::bke::mesh_calc_edges(*blender_mesh, false, false);
/* We must apply custom normals after edges have been calculated, because
* bke::mesh_set_custom_normals()'s internals expect mesh->medge to be populated
* and for the MLoops to have correct edge indices. */
if (use_custom_normals && !loop_normals.is_empty()) {
/* bke::mesh_set_custom_normals()'s internals also expect that each corner
* has a valid vertex index, which may not be the case due to the existing
* logic in read_faces(). This check isn't necessary in the no-custom-normals
* case because the invalid MLoops get stripped in a later step. */
if (bc_has_out_of_bound_indices(blender_mesh)) {
fprintf(stderr, "Can't apply custom normals, encountered invalid loop vert indices!\n");
}
/* There may be a mismatch in lengths if one or more of the MeshPrimitives in
* the Geometry had missing or otherwise invalid normals. */
else if (blender_mesh->corners_num != loop_normals.size()) {
fprintf(stderr,
"Can't apply custom normals, mesh->corners_num != loop_normals.size() (%d != %d)\n",
blender_mesh->corners_num,
int(loop_normals.size()));
}
else {
bke::mesh_set_custom_normals(*blender_mesh, loop_normals);
}
}
/* read_lines() must be called after the face edges have been generated.
* Otherwise the loose edges will be silently deleted again. */
read_lines(mesh, blender_mesh);
return true;
}