diff options
Diffstat (limited to 'source/blender/io')
48 files changed, 4937 insertions, 3398 deletions
diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h index 3d1391ac2a4..0a3a43bb21f 100644 --- a/source/blender/io/alembic/ABC_alembic.h +++ b/source/blender/io/alembic/ABC_alembic.h @@ -25,6 +25,7 @@ extern "C" { #endif +struct CacheArchiveHandle; struct CacheReader; struct ListBase; struct Main; @@ -33,8 +34,6 @@ struct Object; struct Scene; struct bContext; -typedef struct AbcArchiveHandle AbcArchiveHandle; - int ABC_get_version(void); struct AlembicExportParams { @@ -98,13 +97,14 @@ bool ABC_import(struct bContext *C, int sequence_len, int offset, bool validate_meshes, + bool always_add_cache_reader, bool as_background_job); -AbcArchiveHandle *ABC_create_handle(struct Main *bmain, - const char *filename, - struct ListBase *object_paths); +struct CacheArchiveHandle *ABC_create_handle(struct Main *bmain, + const char *filename, + struct ListBase *object_paths); -void ABC_free_handle(AbcArchiveHandle *handle); +void ABC_free_handle(struct CacheArchiveHandle *handle); void ABC_get_transform(struct CacheReader *reader, float r_mat_world[4][4], @@ -125,10 +125,10 @@ bool ABC_mesh_topology_changed(struct CacheReader *reader, const float time, const char **err_str); -void CacheReader_incref(struct CacheReader *reader); -void CacheReader_free(struct CacheReader *reader); +void ABC_CacheReader_incref(struct CacheReader *reader); +void ABC_CacheReader_free(struct CacheReader *reader); -struct CacheReader *CacheReader_open_alembic_object(struct AbcArchiveHandle *handle, +struct CacheReader *CacheReader_open_alembic_object(struct CacheArchiveHandle *handle, struct CacheReader *reader, struct Object *object, const char *object_path); diff --git a/source/blender/io/alembic/intern/abc_reader_curves.cc b/source/blender/io/alembic/intern/abc_reader_curves.cc index 8d6605d6973..27ee35d1b39 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.cc +++ b/source/blender/io/alembic/intern/abc_reader_curves.cc @@ -112,7 +112,7 @@ void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSele read_curve_sample(cu, m_curves_schema, sample_sel); - if (has_animations(m_curves_schema, m_settings)) { + if (m_settings->always_add_cache_reader || has_animations(m_curves_schema, m_settings)) { addCacheModifier(); } } diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index c05df7f1ff5..77edd4908bd 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -578,7 +578,7 @@ void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec readFaceSetsSample(bmain, mesh, sample_sel); - if (has_animations(m_schema, m_settings)) { + if (m_settings->always_add_cache_reader || has_animations(m_schema, m_settings)) { addCacheModifier(); } } @@ -928,7 +928,7 @@ void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec BKE_mesh_validate(mesh, false, false); } - if (has_animations(m_schema, m_settings)) { + if (m_settings->always_add_cache_reader || has_animations(m_schema, m_settings)) { addCacheModifier(); } } diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.cc b/source/blender/io/alembic/intern/abc_reader_nurbs.cc index 2a5f4ecb787..25567aa8c24 100644 --- a/source/blender/io/alembic/intern/abc_reader_nurbs.cc +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.cc @@ -90,7 +90,7 @@ static bool set_knots(const FloatArraySamplePtr &knots, float *&nu_knots) void AbcNurbsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) { - Curve *cu = static_cast<Curve *>(BKE_curve_add(bmain, "abc_curve", OB_SURF)); + Curve *cu = static_cast<Curve *>(BKE_curve_add(bmain, m_data_name.c_str(), OB_SURF)); cu->actvert = CU_ACT_NONE; std::vector<std::pair<INuPatchSchema, IObject>>::iterator it; @@ -180,8 +180,6 @@ void AbcNurbsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSele BLI_addtail(BKE_curve_nurbs_get(cu), nu); } - BLI_strncpy(cu->id.name + 2, m_data_name.c_str(), m_data_name.size() + 1); - m_object = BKE_object_add_only_object(bmain, OB_SURF, m_object_name.c_str()); m_object->data = cu; } diff --git a/source/blender/io/alembic/intern/abc_reader_object.cc b/source/blender/io/alembic/intern/abc_reader_object.cc index 00b73d29c5c..9a5ffd04bd1 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.cc +++ b/source/blender/io/alembic/intern/abc_reader_object.cc @@ -1,4 +1,4 @@ -/* +/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 @@ -197,7 +197,7 @@ void AbcObjectReader::setupObjectTransform(const float time) BKE_object_apply_mat4(m_object, transform_from_alembic, true, false); BKE_object_to_mat4(m_object, m_object->obmat); - if (!is_constant) { + if (!is_constant || m_settings->always_add_cache_reader) { bConstraint *con = BKE_constraint_add_for_object( m_object, nullptr, CONSTRAINT_TYPE_TRANSFORM_CACHE); bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data); diff --git a/source/blender/io/alembic/intern/abc_reader_object.h b/source/blender/io/alembic/intern/abc_reader_object.h index dacdcf3f722..89590b26b61 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.h +++ b/source/blender/io/alembic/intern/abc_reader_object.h @@ -51,6 +51,7 @@ struct ImportSettings { int read_flag; bool validate_meshes; + bool always_add_cache_reader; CacheFile *cache_file; @@ -65,6 +66,7 @@ struct ImportSettings { sequence_offset(0), read_flag(0), validate_meshes(false), + always_add_cache_reader(false), cache_file(NULL) { } diff --git a/source/blender/io/alembic/intern/abc_reader_points.cc b/source/blender/io/alembic/intern/abc_reader_points.cc index f7dcba7a0de..3aeacbd14fe 100644 --- a/source/blender/io/alembic/intern/abc_reader_points.cc +++ b/source/blender/io/alembic/intern/abc_reader_points.cc @@ -95,7 +95,7 @@ void AbcPointsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSel m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str()); m_object->data = mesh; - if (has_animations(m_schema, m_settings)) { + if (m_settings->always_add_cache_reader || has_animations(m_schema, m_settings)) { addCacheModifier(); } } diff --git a/source/blender/io/alembic/intern/abc_util.h b/source/blender/io/alembic/intern/abc_util.h index 98f4b0376a7..ced9fde0f85 100644 --- a/source/blender/io/alembic/intern/abc_util.h +++ b/source/blender/io/alembic/intern/abc_util.h @@ -22,15 +22,6 @@ #include <Alembic/Abc/All.h> #include <Alembic/AbcGeom/All.h> -/** - * \brief The CacheReader struct is only used for anonymous pointers, - * to interface between C and C++ code. This library only creates - * pointers to AbcObjectReader (or subclasses thereof). - */ -struct CacheReader { - int unused; -}; - using Alembic::Abc::chrono_t; struct ID; diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc index e8d70bf3edb..deb945b767c 100644 --- a/source/blender/io/alembic/intern/alembic_capi.cc +++ b/source/blender/io/alembic/intern/alembic_capi.cc @@ -19,6 +19,7 @@ */ #include "../ABC_alembic.h" +#include "IO_types.h" #include <Alembic/AbcMaterial/IMaterial.h> @@ -89,18 +90,14 @@ using Alembic::AbcMaterial::IMaterial; using namespace blender::io::alembic; -struct AbcArchiveHandle { - int unused; -}; - -BLI_INLINE ArchiveReader *archive_from_handle(AbcArchiveHandle *handle) +BLI_INLINE ArchiveReader *archive_from_handle(CacheArchiveHandle *handle) { return reinterpret_cast<ArchiveReader *>(handle); } -BLI_INLINE AbcArchiveHandle *handle_from_archive(ArchiveReader *archive) +BLI_INLINE CacheArchiveHandle *handle_from_archive(ArchiveReader *archive) { - return reinterpret_cast<AbcArchiveHandle *>(archive); + return reinterpret_cast<CacheArchiveHandle *>(archive); } //#define USE_NURBS @@ -150,8 +147,8 @@ static bool gather_objects_paths(const IObject &object, ListBase *object_paths) } if (get_path) { - void *abc_path_void = MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath"); - AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>(abc_path_void); + void *abc_path_void = MEM_callocN(sizeof(CacheObjectPath), "CacheObjectPath"); + CacheObjectPath *abc_path = static_cast<CacheObjectPath *>(abc_path_void); BLI_strncpy(abc_path->path, object.getFullName().c_str(), sizeof(abc_path->path)); BLI_addtail(object_paths, abc_path); @@ -160,9 +157,9 @@ static bool gather_objects_paths(const IObject &object, ListBase *object_paths) return parent_is_part_of_this_object; } -AbcArchiveHandle *ABC_create_handle(struct Main *bmain, - const char *filename, - ListBase *object_paths) +CacheArchiveHandle *ABC_create_handle(struct Main *bmain, + const char *filename, + ListBase *object_paths) { ArchiveReader *archive = new ArchiveReader(bmain, filename); @@ -178,7 +175,7 @@ AbcArchiveHandle *ABC_create_handle(struct Main *bmain, return handle_from_archive(archive); } -void ABC_free_handle(AbcArchiveHandle *handle) +void ABC_free_handle(CacheArchiveHandle *handle) { delete archive_from_handle(handle); } @@ -359,8 +356,8 @@ static std::pair<bool, AbcObjectReader *> visit_object( readers.push_back(reader); reader->incref(); - AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>( - MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath")); + CacheObjectPath *abc_path = static_cast<CacheObjectPath *>( + MEM_callocN(sizeof(CacheObjectPath), "CacheObjectPath")); BLI_strncpy(abc_path->path, full_name.c_str(), sizeof(abc_path->path)); BLI_addtail(&settings.cache_file->object_paths, abc_path); @@ -666,6 +663,7 @@ bool ABC_import(bContext *C, int sequence_len, int offset, bool validate_meshes, + bool always_add_cache_reader, bool as_background_job) { /* Using new here since MEM_* functions do not call constructor to properly initialize data. */ @@ -684,6 +682,7 @@ bool ABC_import(bContext *C, job->settings.sequence_len = sequence_len; job->settings.sequence_offset = offset; job->settings.validate_meshes = validate_meshes; + job->settings.always_add_cache_reader = always_add_cache_reader; job->error_code = ABC_NO_ERROR; job->was_cancelled = false; job->archive = nullptr; @@ -812,7 +811,7 @@ bool ABC_mesh_topology_changed( /* ************************************************************************** */ -void CacheReader_free(CacheReader *reader) +void ABC_CacheReader_free(CacheReader *reader) { AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); abc_reader->decref(); @@ -822,13 +821,13 @@ void CacheReader_free(CacheReader *reader) } } -void CacheReader_incref(CacheReader *reader) +void ABC_CacheReader_incref(CacheReader *reader) { AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader); abc_reader->incref(); } -CacheReader *CacheReader_open_alembic_object(AbcArchiveHandle *handle, +CacheReader *CacheReader_open_alembic_object(CacheArchiveHandle *handle, CacheReader *reader, Object *object, const char *object_path) @@ -847,7 +846,7 @@ CacheReader *CacheReader_open_alembic_object(AbcArchiveHandle *handle, find_iobject(archive->getTop(), iobject, object_path); if (reader) { - CacheReader_free(reader); + ABC_CacheReader_free(reader); } ImportSettings settings; diff --git a/source/blender/io/avi/intern/avi_endian.c b/source/blender/io/avi/intern/avi_endian.c index 146206cd917..36cee7bcadc 100644 --- a/source/blender/io/avi/intern/avi_endian.c +++ b/source/blender/io/avi/intern/avi_endian.c @@ -42,7 +42,7 @@ static void invert(int *val) { int tval = *val; - *val = ((tval >> 24)) | ((tval << 8) & 0x00ff0000) | ((tval >> 8) & 0x0000ff00) | ((tval << 24)); + *val = (tval >> 24) | ((tval << 8) & 0x00ff0000) | ((tval >> 8) & 0x0000ff00) | (tval << 24); } static void sinvert(short int *val) diff --git a/source/blender/io/collada/AnimationImporter.cpp b/source/blender/io/collada/AnimationImporter.cpp index e52bdca0d87..e54192abc54 100644 --- a/source/blender/io/collada/AnimationImporter.cpp +++ b/source/blender/io/collada/AnimationImporter.cpp @@ -1363,7 +1363,7 @@ void AnimationImporter::add_bone_animation_sampled(Object *ob, calc_joint_parent_mat_rest(par, nullptr, root, node); mul_m4_m4m4(temp, par, matfra); - /* evaluate_joint_world_transform_at_frame(temp, NULL, node, fra); */ + // evaluate_joint_world_transform_at_frame(temp, NULL, node, fra); /* calc special matrix */ mul_m4_series(mat, irest, temp, irest_dae, rest); diff --git a/source/blender/io/collada/SceneExporter.cpp b/source/blender/io/collada/SceneExporter.cpp index 5bbd22b8275..09da2288cfc 100644 --- a/source/blender/io/collada/SceneExporter.cpp +++ b/source/blender/io/collada/SceneExporter.cpp @@ -172,7 +172,7 @@ void SceneExporter::writeNode(Object *ob) else if (ob->type == OB_EMPTY) { /* TODO: handle groups (OB_DUPLICOLLECTION */ if ((ob->transflag & OB_DUPLICOLLECTION) == OB_DUPLICOLLECTION && ob->instance_collection) { Collection *collection = ob->instance_collection; - /* printf("group detected '%s'\n", group->id.name + 2); */ + // printf("group detected '%s'\n", group->id.name + 2); FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, object) { printf("\t%s\n", object->id.name); } diff --git a/source/blender/io/collada/collada_internal.cpp b/source/blender/io/collada/collada_internal.cpp index 355aa5c22f0..bd6f496c8ec 100644 --- a/source/blender/io/collada/collada_internal.cpp +++ b/source/blender/io/collada/collada_internal.cpp @@ -162,7 +162,7 @@ void UnitConverter::calculate_scale(Scene &sce) * Translation map. * Used to translate every COLLADA id to a valid id, no matter what "wrong" letters may be * included. Look at the IDREF XSD declaration for more. - * Follows strictly the COLLADA XSD declaration which explicitly allows non-english chars, + * Follows strictly the COLLADA XSD declaration which explicitly allows non-English chars, * like special chars (e.g. micro sign), umlauts and so on. * The COLLADA spec also allows additional chars for member access ('.'), these * must obviously be removed too, otherwise they would be heavily misinterpreted. diff --git a/source/blender/io/common/CMakeLists.txt b/source/blender/io/common/CMakeLists.txt index 7e39af32f11..2aaf5d57fd6 100644 --- a/source/blender/io/common/CMakeLists.txt +++ b/source/blender/io/common/CMakeLists.txt @@ -37,6 +37,7 @@ set(SRC IO_abstract_hierarchy_iterator.h IO_dupli_persistent_id.hh + IO_types.h intern/dupli_parent_finder.hh ) diff --git a/source/blender/io/common/IO_types.h b/source/blender/io/common/IO_types.h new file mode 100644 index 00000000000..4570e29f6ed --- /dev/null +++ b/source/blender/io/common/IO_types.h @@ -0,0 +1,34 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ +#pragma once + +/* The CacheArchiveHandle struct is only used for anonymous pointers, + * to interface between C and C++ code. This is currently used + * to hide pointers to alembic ArchiveReader and USDStageReader. */ +struct CacheArchiveHandle { + int unused; +}; + +/* The CacheReader struct is only used for anonymous pointers, + * to interface between C and C++ code. This is currently used + * to hide pointers to AbcObjectReader and USDPrimReader + * (or subclasses thereof). */ +struct CacheReader { + int unused; +}; diff --git a/source/blender/io/gpencil/CMakeLists.txt b/source/blender/io/gpencil/CMakeLists.txt index fec95be6aa8..4af8b506bd5 100644 --- a/source/blender/io/gpencil/CMakeLists.txt +++ b/source/blender/io/gpencil/CMakeLists.txt @@ -33,6 +33,7 @@ set(INC ../../../../intern/clog ../../../../intern/guardedalloc ../../../../intern/utfconv + ../../../../extern/nanosvg ) set(INC_SYS @@ -44,9 +45,6 @@ set(SRC intern/gpencil_io_import_base.cc intern/gpencil_io_import_svg.cc - # This line must be removed if NanoSVG is moved to extern - nanosvg/nanosvg.h - gpencil_io.h intern/gpencil_io_base.hh intern/gpencil_io_export_base.hh diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc index db6bbc7768e..941d1137f4d 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc @@ -42,7 +42,7 @@ #define NANOSVG_ALL_COLOR_KEYWORDS #define NANOSVG_IMPLEMENTATION -#include "nanosvg/nanosvg.h" +#include "nanosvg.h" using blender::MutableSpan; diff --git a/source/blender/io/gpencil/nanosvg/nanosvg.h b/source/blender/io/gpencil/nanosvg/nanosvg.h deleted file mode 100644 index 94dad37861a..00000000000 --- a/source/blender/io/gpencil/nanosvg/nanosvg.h +++ /dev/null @@ -1,3327 +0,0 @@ -/* - * Copyright (c) 2013-14 `Mikko Mononen <memon@inside.org>` - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example - * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) - * - * Arc calculation code based on canvg (https://code.google.com/p/canvg/) - * - * Bounding box calculation based on - * http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html - * - * This is a modified version for Blender used by importers. - * - */ - -#ifndef NANOSVG_H -#define NANOSVG_H - -#ifndef NANOSVG_CPLUSPLUS -# ifdef __cplusplus -extern "C" { -# endif -#endif - -// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of -// cubic bezier shapes. -// -// The library suits well for anything from rendering scalable icons in your editor application to -// prototyping a game. -// -// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create -// a pull request! -// -// The shapes in the SVG images are transformed by the viewBox and converted to specified units. -// That is, you should get the same looking data as your designed in your favorite app. -// -// NanoSVG can return the paths in few different units. For example if you want to render an image, -// you may choose to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you -// may want to use millimeters. -// -// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. -// DPI (dots-per-inch) controls how the unit conversion is done. -// -// If you don't know or care about the units stuff, "px" and 96 should get you going. - -/* Example Usage: - // Load SVG - NSVGimage* image; - image = nsvgParseFromFile("test.svg", "px", 96); - printf("size: %f x %f\n", image->width, image->height); - // Use... - for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { - for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { - for (int i = 0; i < path->npts-1; i += 3) { - float* p = &path->pts[i*2]; - drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); - } - } - } - // Delete - nsvgDelete(image); -*/ - -enum NSVGpaintType { - NSVG_PAINT_NONE = 0, - NSVG_PAINT_COLOR = 1, - NSVG_PAINT_LINEAR_GRADIENT = 2, - NSVG_PAINT_RADIAL_GRADIENT = 3 -}; - -enum NSVGspreadType { NSVG_SPREAD_PAD = 0, NSVG_SPREAD_REFLECT = 1, NSVG_SPREAD_REPEAT = 2 }; - -enum NSVGlineJoin { NSVG_JOIN_MITER = 0, NSVG_JOIN_ROUND = 1, NSVG_JOIN_BEVEL = 2 }; - -enum NSVGlineCap { NSVG_CAP_BUTT = 0, NSVG_CAP_ROUND = 1, NSVG_CAP_SQUARE = 2 }; - -enum NSVGfillRule { NSVG_FILLRULE_NONZERO = 0, NSVG_FILLRULE_EVENODD = 1 }; - -enum NSVGflags { NSVG_FLAGS_VISIBLE = 0x01 }; - -typedef struct NSVGgradientStop { - unsigned int color; - float offset; -} NSVGgradientStop; - -typedef struct NSVGgradient { - float xform[6]; - char spread; - float fx, fy; - int nstops; - NSVGgradientStop stops[1]; -} NSVGgradient; - -typedef struct NSVGpaint { - char type; - union { - unsigned int color; - NSVGgradient *gradient; - }; -} NSVGpaint; - -typedef struct NSVGpath { - float *pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... - int npts; // Total number of bezier points. - char closed; // Flag indicating if shapes should be treated as closed. - float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. - struct NSVGpath *next; // Pointer to next path, or NULL if last element. -} NSVGpath; - -typedef struct NSVGshape { - char id[64]; // Optional 'id' attr of the shape or its group - /* Blender: Parent ID used for layer creation. */ - char id_parent[64]; - NSVGpaint fill; // Fill paint - NSVGpaint stroke; // Stroke paint - float opacity; // Opacity of the shape. - float strokeWidth; // Stroke width (scaled). - float strokeDashOffset; // Stroke dash offset (scaled). - float strokeDashArray[8]; // Stroke dash array (scaled). - char strokeDashCount; // Number of dash values in dash array. - char strokeLineJoin; // Stroke join type. - char strokeLineCap; // Stroke cap type. - float miterLimit; // Miter limit - char fillRule; // Fill rule, see NSVGfillRule. - unsigned char flags; // Logical or of NSVG_FLAGS_* flags - float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. - NSVGpath *paths; // Linked list of paths in the image. - struct NSVGshape *next; // Pointer to next shape, or NULL if last element. -} NSVGshape; - -typedef struct NSVGimage { - float width; // Width of the image. - float height; // Height of the image. - NSVGshape *shapes; // Linked list of shapes in the image. -} NSVGimage; - -// Parses SVG file from a file, returns SVG image as paths. -NSVGimage *nsvgParseFromFile(const char *filename, const char *units, float dpi); - -// Parses SVG file from a null terminated string, returns SVG image as paths. -// Important note: changes the string. -NSVGimage *nsvgParse(char *input, const char *units, float dpi); - -// Duplicates a path. -NSVGpath *nsvgDuplicatePath(NSVGpath *p); - -// Deletes an image. -void nsvgDelete(NSVGimage *image); - -#ifndef NANOSVG_CPLUSPLUS -# ifdef __cplusplus -} -# endif -#endif - -#endif // NANOSVG_H - -#ifdef NANOSVG_IMPLEMENTATION - -#include <math.h> -#include <stdlib.h> -#include <string.h> - -#define NSVG_PI (3.14159265358979323846264338327f) -#define NSVG_KAPPA90 \ - (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. - -#define NSVG_ALIGN_MIN 0 -#define NSVG_ALIGN_MID 1 -#define NSVG_ALIGN_MAX 2 -#define NSVG_ALIGN_NONE 0 -#define NSVG_ALIGN_MEET 1 -#define NSVG_ALIGN_SLICE 2 - -#define NSVG_NOTUSED(v) \ - do { \ - (void)(1 ? (void)0 : ((void)(v))); \ - } while (0) -#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) - -#ifdef _MSC_VER -# pragma warning(disable : 4996) // Switch off security warnings -# pragma warning(disable : 4100) // Switch off unreferenced formal parameter warnings -# ifdef __cplusplus -# define NSVG_INLINE inline -# else -# define NSVG_INLINE -# endif -#else -# define NSVG_INLINE inline -#endif - -static int nsvg__isspace(char c) -{ - return strchr(" \t\n\v\f\r", c) != 0; -} - -static int nsvg__isdigit(char c) -{ - return c >= '0' && c <= '9'; -} - -static NSVG_INLINE float nsvg__minf(float a, float b) -{ - return a < b ? a : b; -} -static NSVG_INLINE float nsvg__maxf(float a, float b) -{ - return a > b ? a : b; -} - -// Simple XML parser - -#define NSVG_XML_TAG 1 -#define NSVG_XML_CONTENT 2 -#define NSVG_XML_MAX_ATTRIBS 256 - -static void nsvg__parseContent(char *s, void (*contentCb)(void *ud, const char *s), void *ud) -{ - // Trim start white spaces - while (*s && nsvg__isspace(*s)) - s++; - if (!*s) - return; - - if (contentCb) - (*contentCb)(ud, s); -} - -static void nsvg__parseElement(char *s, - void (*startelCb)(void *ud, const char *el, const char **attr), - void (*endelCb)(void *ud, const char *el), - void *ud) -{ - const char *attr[NSVG_XML_MAX_ATTRIBS]; - int nattr = 0; - char *name; - int start = 0; - int end = 0; - char quote; - - // Skip white space after the '<' - while (*s && nsvg__isspace(*s)) - s++; - - // Check if the tag is end tag - if (*s == '/') { - s++; - end = 1; - } - else { - start = 1; - } - - // Skip comments, data and preprocessor stuff. - if (!*s || *s == '?' || *s == '!') - return; - - // Get tag name - name = s; - while (*s && !nsvg__isspace(*s)) - s++; - if (*s) { - *s++ = '\0'; - } - - // Get attribs - while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS - 3) { - char *name = NULL; - char *value = NULL; - - // Skip white space before the attrib name - while (*s && nsvg__isspace(*s)) - s++; - if (!*s) - break; - if (*s == '/') { - end = 1; - break; - } - name = s; - // Find end of the attrib name. - while (*s && !nsvg__isspace(*s) && *s != '=') - s++; - if (*s) { - *s++ = '\0'; - } - // Skip until the beginning of the value. - while (*s && *s != '\"' && *s != '\'') - s++; - if (!*s) - break; - quote = *s; - s++; - // Store value and find the end of it. - value = s; - while (*s && *s != quote) - s++; - if (*s) { - *s++ = '\0'; - } - - // Store only well formed attributes - if (name && value) { - attr[nattr++] = name; - attr[nattr++] = value; - } - } - - // List terminator - attr[nattr++] = 0; - attr[nattr++] = 0; - - // Call callbacks. - if (start && startelCb) - (*startelCb)(ud, name, attr); - if (end && endelCb) - (*endelCb)(ud, name); -} - -static int nsvg__parseXML(char *input, - void (*startelCb)(void *ud, const char *el, const char **attr), - void (*endelCb)(void *ud, const char *el), - void (*contentCb)(void *ud, const char *s), - void *ud) -{ - char *s = input; - char *mark = s; - int state = NSVG_XML_CONTENT; - while (*s) { - if (*s == '<' && state == NSVG_XML_CONTENT) { - // Start of a tag - *s++ = '\0'; - nsvg__parseContent(mark, contentCb, ud); - mark = s; - state = NSVG_XML_TAG; - } - else if (*s == '>' && state == NSVG_XML_TAG) { - // Start of a content or new tag. - *s++ = '\0'; - nsvg__parseElement(mark, startelCb, endelCb, ud); - mark = s; - state = NSVG_XML_CONTENT; - } - else { - s++; - } - } - - return 1; -} - -/* Simple SVG parser. */ - -#define NSVG_MAX_ATTR 128 -#define NSVG_MAX_BREADCRUMB 5 - -enum NSVGgradientUnits { NSVG_USER_SPACE = 0, NSVG_OBJECT_SPACE = 1 }; - -#define NSVG_MAX_DASHES 8 - -enum NSVGunits { - NSVG_UNITS_USER, - NSVG_UNITS_PX, - NSVG_UNITS_PT, - NSVG_UNITS_PC, - NSVG_UNITS_MM, - NSVG_UNITS_CM, - NSVG_UNITS_IN, - NSVG_UNITS_PERCENT, - NSVG_UNITS_EM, - NSVG_UNITS_EX -}; - -typedef struct NSVGcoordinate { - float value; - int units; -} NSVGcoordinate; - -typedef struct NSVGlinearData { - NSVGcoordinate x1, y1, x2, y2; -} NSVGlinearData; - -typedef struct NSVGradialData { - NSVGcoordinate cx, cy, r, fx, fy; -} NSVGradialData; - -typedef struct NSVGgradientData { - char id[64]; - char ref[64]; - char type; - union { - NSVGlinearData linear; - NSVGradialData radial; - }; - char spread; - char units; - float xform[6]; - int nstops; - NSVGgradientStop *stops; - struct NSVGgradientData *next; -} NSVGgradientData; - -typedef struct NSVGattrib { - char id[64]; - float xform[6]; - unsigned int fillColor; - unsigned int strokeColor; - float opacity; - float fillOpacity; - float strokeOpacity; - char fillGradient[64]; - char strokeGradient[64]; - float strokeWidth; - float strokeDashOffset; - float strokeDashArray[NSVG_MAX_DASHES]; - int strokeDashCount; - char strokeLineJoin; - char strokeLineCap; - float miterLimit; - char fillRule; - float fontSize; - unsigned int stopColor; - float stopOpacity; - float stopOffset; - char hasFill; - char hasStroke; - char visible; -} NSVGattrib; - -typedef struct NSVGparser { - NSVGattrib attr[NSVG_MAX_ATTR]; - int attrHead; - float *pts; - int npts; - int cpts; - NSVGpath *plist; - NSVGimage *image; - NSVGgradientData *gradients; - NSVGshape *shapesTail; - float viewMinx, viewMiny, viewWidth, viewHeight; - int alignX, alignY, alignType; - float dpi; - char pathFlag; - char defsFlag; - /** Blender breadcrumb for layers. */ - char breadcrumb[NSVG_MAX_BREADCRUMB][64]; - /** Blender number of elements in breadcrumb. */ - int breadcrumb_len; -} NSVGparser; - -static void nsvg__xformIdentity(float *t) -{ - t[0] = 1.0f; - t[1] = 0.0f; - t[2] = 0.0f; - t[3] = 1.0f; - t[4] = 0.0f; - t[5] = 0.0f; -} - -static void nsvg__xformSetTranslation(float *t, float tx, float ty) -{ - t[0] = 1.0f; - t[1] = 0.0f; - t[2] = 0.0f; - t[3] = 1.0f; - t[4] = tx; - t[5] = ty; -} - -static void nsvg__xformSetScale(float *t, float sx, float sy) -{ - t[0] = sx; - t[1] = 0.0f; - t[2] = 0.0f; - t[3] = sy; - t[4] = 0.0f; - t[5] = 0.0f; -} - -static void nsvg__xformSetSkewX(float *t, float a) -{ - t[0] = 1.0f; - t[1] = 0.0f; - t[2] = tanf(a); - t[3] = 1.0f; - t[4] = 0.0f; - t[5] = 0.0f; -} - -static void nsvg__xformSetSkewY(float *t, float a) -{ - t[0] = 1.0f; - t[1] = tanf(a); - t[2] = 0.0f; - t[3] = 1.0f; - t[4] = 0.0f; - t[5] = 0.0f; -} - -static void nsvg__xformSetRotation(float *t, float a) -{ - float cs = cosf(a), sn = sinf(a); - t[0] = cs; - t[1] = sn; - t[2] = -sn; - t[3] = cs; - t[4] = 0.0f; - t[5] = 0.0f; -} - -static void nsvg__xformMultiply(float *t, float *s) -{ - float t0 = t[0] * s[0] + t[1] * s[2]; - float t2 = t[2] * s[0] + t[3] * s[2]; - float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; - t[1] = t[0] * s[1] + t[1] * s[3]; - t[3] = t[2] * s[1] + t[3] * s[3]; - t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; - t[0] = t0; - t[2] = t2; - t[4] = t4; -} - -static void nsvg__xformInverse(float *inv, float *t) -{ - double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; - if (det > -1e-6 && det < 1e-6) { - nsvg__xformIdentity(t); - return; - } - invdet = 1.0 / det; - inv[0] = (float)(t[3] * invdet); - inv[2] = (float)(-t[2] * invdet); - inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); - inv[1] = (float)(-t[1] * invdet); - inv[3] = (float)(t[0] * invdet); - inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); -} - -static void nsvg__xformPremultiply(float *t, float *s) -{ - float s2[6]; - memcpy(s2, s, sizeof(float) * 6); - nsvg__xformMultiply(s2, t); - memcpy(t, s2, sizeof(float) * 6); -} - -static void nsvg__xformPoint(float *dx, float *dy, float x, float y, float *t) -{ - *dx = x * t[0] + y * t[2] + t[4]; - *dy = x * t[1] + y * t[3] + t[5]; -} - -static void nsvg__xformVec(float *dx, float *dy, float x, float y, float *t) -{ - *dx = x * t[0] + y * t[2]; - *dy = x * t[1] + y * t[3]; -} - -#define NSVG_EPSILON (1e-12) - -static int nsvg__ptInBounds(float *pt, float *bounds) -{ - return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; -} - -static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) -{ - double it = 1.0 - t; - return it * it * it * p0 + 3.0 * it * it * t * p1 + 3.0 * it * t * t * p2 + t * t * t * p3; -} - -static void nsvg__curveBounds(float *bounds, float *curve) -{ - int i, j, count; - double roots[2], a, b, c, b2ac, t, v; - float *v0 = &curve[0]; - float *v1 = &curve[2]; - float *v2 = &curve[4]; - float *v3 = &curve[6]; - - // Start the bounding box by end points - bounds[0] = nsvg__minf(v0[0], v3[0]); - bounds[1] = nsvg__minf(v0[1], v3[1]); - bounds[2] = nsvg__maxf(v0[0], v3[0]); - bounds[3] = nsvg__maxf(v0[1], v3[1]); - - // Bezier curve fits inside the convex hull of it's control points. - // If control points are inside the bounds, we're done. - if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) - return; - - // Add bezier curve inflection points in X and Y. - for (i = 0; i < 2; i++) { - a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; - b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; - c = 3.0 * v1[i] - 3.0 * v0[i]; - count = 0; - if (fabs(a) < NSVG_EPSILON) { - if (fabs(b) > NSVG_EPSILON) { - t = -c / b; - if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON) - roots[count++] = t; - } - } - else { - b2ac = b * b - 4.0 * c * a; - if (b2ac > NSVG_EPSILON) { - t = (-b + sqrt(b2ac)) / (2.0 * a); - if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON) - roots[count++] = t; - t = (-b - sqrt(b2ac)) / (2.0 * a); - if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON) - roots[count++] = t; - } - } - for (j = 0; j < count; j++) { - v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); - bounds[0 + i] = nsvg__minf(bounds[0 + i], (float)v); - bounds[2 + i] = nsvg__maxf(bounds[2 + i], (float)v); - } - } -} - -static NSVGparser *nsvg__createParser() -{ - NSVGparser *p; - p = (NSVGparser *)malloc(sizeof(NSVGparser)); - if (p == NULL) - goto error; - memset(p, 0, sizeof(NSVGparser)); - - p->image = (NSVGimage *)malloc(sizeof(NSVGimage)); - if (p->image == NULL) - goto error; - memset(p->image, 0, sizeof(NSVGimage)); - - // Init style - nsvg__xformIdentity(p->attr[0].xform); - memset(p->attr[0].id, 0, sizeof p->attr[0].id); - p->attr[0].fillColor = NSVG_RGB(0, 0, 0); - p->attr[0].strokeColor = NSVG_RGB(0, 0, 0); - p->attr[0].opacity = 1; - p->attr[0].fillOpacity = 1; - p->attr[0].strokeOpacity = 1; - p->attr[0].stopOpacity = 1; - p->attr[0].strokeWidth = 1; - p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; - p->attr[0].strokeLineCap = NSVG_CAP_BUTT; - p->attr[0].miterLimit = 4; - p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; - p->attr[0].hasFill = 1; - p->attr[0].visible = 1; - - return p; - -error: - if (p) { - if (p->image) - free(p->image); - free(p); - } - return NULL; -} - -static void nsvg__deletePaths(NSVGpath *path) -{ - while (path) { - NSVGpath *next = path->next; - if (path->pts != NULL) - free(path->pts); - free(path); - path = next; - } -} - -static void nsvg__deletePaint(NSVGpaint *paint) -{ - if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) - free(paint->gradient); -} - -static void nsvg__deleteGradientData(NSVGgradientData *grad) -{ - NSVGgradientData *next; - while (grad != NULL) { - next = grad->next; - free(grad->stops); - free(grad); - grad = next; - } -} - -static void nsvg__deleteParser(NSVGparser *p) -{ - if (p != NULL) { - nsvg__deletePaths(p->plist); - nsvg__deleteGradientData(p->gradients); - nsvgDelete(p->image); - free(p->pts); - free(p); - } -} - -static void nsvg__resetPath(NSVGparser *p) -{ - p->npts = 0; -} - -static void nsvg__addPoint(NSVGparser *p, float x, float y) -{ - if (p->npts + 1 > p->cpts) { - p->cpts = p->cpts ? p->cpts * 2 : 8; - p->pts = (float *)realloc(p->pts, p->cpts * 2 * sizeof(float)); - if (!p->pts) - return; - } - p->pts[p->npts * 2 + 0] = x; - p->pts[p->npts * 2 + 1] = y; - p->npts++; -} - -static void nsvg__moveTo(NSVGparser *p, float x, float y) -{ - if (p->npts > 0) { - p->pts[(p->npts - 1) * 2 + 0] = x; - p->pts[(p->npts - 1) * 2 + 1] = y; - } - else { - nsvg__addPoint(p, x, y); - } -} - -static void nsvg__lineTo(NSVGparser *p, float x, float y) -{ - float px, py, dx, dy; - if (p->npts > 0) { - px = p->pts[(p->npts - 1) * 2 + 0]; - py = p->pts[(p->npts - 1) * 2 + 1]; - dx = x - px; - dy = y - py; - nsvg__addPoint(p, px + dx / 3.0f, py + dy / 3.0f); - nsvg__addPoint(p, x - dx / 3.0f, y - dy / 3.0f); - nsvg__addPoint(p, x, y); - } -} - -static void nsvg__cubicBezTo( - NSVGparser *p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) -{ - if (p->npts > 0) { - nsvg__addPoint(p, cpx1, cpy1); - nsvg__addPoint(p, cpx2, cpy2); - nsvg__addPoint(p, x, y); - } -} - -static NSVGattrib *nsvg__getAttr(NSVGparser *p) -{ - return &p->attr[p->attrHead]; -} - -static void nsvg__pushAttr(NSVGparser *p) -{ - if (p->attrHead < NSVG_MAX_ATTR - 1) { - p->attrHead++; - memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead - 1], sizeof(NSVGattrib)); - } -} - -static void nsvg__popAttr(NSVGparser *p) -{ - if (p->attrHead > 0) - p->attrHead--; -} - -static float nsvg__actualOrigX(NSVGparser *p) -{ - return p->viewMinx; -} - -static float nsvg__actualOrigY(NSVGparser *p) -{ - return p->viewMiny; -} - -static float nsvg__actualWidth(NSVGparser *p) -{ - return p->viewWidth; -} - -static float nsvg__actualHeight(NSVGparser *p) -{ - return p->viewHeight; -} - -static float nsvg__actualLength(NSVGparser *p) -{ - float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); - return sqrtf(w * w + h * h) / sqrtf(2.0f); -} - -static float nsvg__convertToPixels(NSVGparser *p, NSVGcoordinate c, float orig, float length) -{ - NSVGattrib *attr = nsvg__getAttr(p); - switch (c.units) { - case NSVG_UNITS_USER: - return c.value; - case NSVG_UNITS_PX: - return c.value; - case NSVG_UNITS_PT: - return c.value / 72.0f * p->dpi; - case NSVG_UNITS_PC: - return c.value / 6.0f * p->dpi; - case NSVG_UNITS_MM: - return c.value / 25.4f * p->dpi; - case NSVG_UNITS_CM: - return c.value / 2.54f * p->dpi; - case NSVG_UNITS_IN: - return c.value * p->dpi; - case NSVG_UNITS_EM: - return c.value * attr->fontSize; - case NSVG_UNITS_EX: - return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. - case NSVG_UNITS_PERCENT: - return orig + c.value / 100.0f * length; - default: - return c.value; - } - return c.value; -} - -static NSVGgradientData *nsvg__findGradientData(NSVGparser *p, const char *id) -{ - NSVGgradientData *grad = p->gradients; - if (id == NULL || *id == '\0') - return NULL; - while (grad != NULL) { - if (strcmp(grad->id, id) == 0) - return grad; - grad = grad->next; - } - return NULL; -} - -static NSVGgradient *nsvg__createGradient(NSVGparser *p, - const char *id, - const float *localBounds, - char *paintType) -{ - NSVGattrib *attr = nsvg__getAttr(p); - NSVGgradientData *data = NULL; - NSVGgradientData *ref = NULL; - NSVGgradientStop *stops = NULL; - NSVGgradient *grad; - float ox, oy, sw, sh, sl; - int nstops = 0; - int refIter; - - data = nsvg__findGradientData(p, id); - if (data == NULL) - return NULL; - - // TODO: use ref to fill in all unset values too. - ref = data; - refIter = 0; - while (ref != NULL) { - NSVGgradientData *nextRef = NULL; - if (stops == NULL && ref->stops != NULL) { - stops = ref->stops; - nstops = ref->nstops; - break; - } - nextRef = nsvg__findGradientData(p, ref->ref); - if (nextRef == ref) - break; // prevent infite loops on malformed data - ref = nextRef; - refIter++; - if (refIter > 32) - break; // prevent infite loops on malformed data - } - if (stops == NULL) - return NULL; - - grad = (NSVGgradient *)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop) * (nstops - 1)); - if (grad == NULL) - return NULL; - - // The shape width and height. - if (data->units == NSVG_OBJECT_SPACE) { - ox = localBounds[0]; - oy = localBounds[1]; - sw = localBounds[2] - localBounds[0]; - sh = localBounds[3] - localBounds[1]; - } - else { - ox = nsvg__actualOrigX(p); - oy = nsvg__actualOrigY(p); - sw = nsvg__actualWidth(p); - sh = nsvg__actualHeight(p); - } - sl = sqrtf(sw * sw + sh * sh) / sqrtf(2.0f); - - if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { - float x1, y1, x2, y2, dx, dy; - x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw); - y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh); - x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw); - y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh); - // Calculate transform aligned to the line - dx = x2 - x1; - dy = y2 - y1; - grad->xform[0] = dy; - grad->xform[1] = -dx; - grad->xform[2] = dx; - grad->xform[3] = dy; - grad->xform[4] = x1; - grad->xform[5] = y1; - } - else { - float cx, cy, fx, fy, r; - cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw); - cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh); - fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw); - fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh); - r = nsvg__convertToPixels(p, data->radial.r, 0, sl); - // Calculate transform aligned to the circle - grad->xform[0] = r; - grad->xform[1] = 0; - grad->xform[2] = 0; - grad->xform[3] = r; - grad->xform[4] = cx; - grad->xform[5] = cy; - grad->fx = fx / r; - grad->fy = fy / r; - } - - nsvg__xformMultiply(grad->xform, data->xform); - nsvg__xformMultiply(grad->xform, attr->xform); - - grad->spread = data->spread; - memcpy(grad->stops, stops, nstops * sizeof(NSVGgradientStop)); - grad->nstops = nstops; - - *paintType = data->type; - - return grad; -} - -static float nsvg__getAverageScale(float *t) -{ - float sx = sqrtf(t[0] * t[0] + t[2] * t[2]); - float sy = sqrtf(t[1] * t[1] + t[3] * t[3]); - return (sx + sy) * 0.5f; -} - -static void nsvg__getLocalBounds(float *bounds, NSVGshape *shape, float *xform) -{ - NSVGpath *path; - float curve[4 * 2], curveBounds[4]; - int i, first = 1; - for (path = shape->paths; path != NULL; path = path->next) { - nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform); - for (i = 0; i < path->npts - 1; i += 3) { - nsvg__xformPoint( - &curve[2], &curve[3], path->pts[(i + 1) * 2], path->pts[(i + 1) * 2 + 1], xform); - nsvg__xformPoint( - &curve[4], &curve[5], path->pts[(i + 2) * 2], path->pts[(i + 2) * 2 + 1], xform); - nsvg__xformPoint( - &curve[6], &curve[7], path->pts[(i + 3) * 2], path->pts[(i + 3) * 2 + 1], xform); - nsvg__curveBounds(curveBounds, curve); - if (first) { - bounds[0] = curveBounds[0]; - bounds[1] = curveBounds[1]; - bounds[2] = curveBounds[2]; - bounds[3] = curveBounds[3]; - first = 0; - } - else { - bounds[0] = nsvg__minf(bounds[0], curveBounds[0]); - bounds[1] = nsvg__minf(bounds[1], curveBounds[1]); - bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]); - bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]); - } - curve[0] = curve[6]; - curve[1] = curve[7]; - } - } -} - -static void nsvg__addShape(NSVGparser *p) -{ - NSVGattrib *attr = nsvg__getAttr(p); - float scale = 1.0f; - NSVGshape *shape; - NSVGpath *path; - int i; - - if (p->plist == NULL) - return; - - shape = (NSVGshape *)malloc(sizeof(NSVGshape)); - if (shape == NULL) - goto error; - memset(shape, 0, sizeof(NSVGshape)); - - memcpy(shape->id, attr->id, sizeof shape->id); - /* Copy parent id from breadcrumb. */ - if (p->breadcrumb_len > 0) { - memcpy(shape->id_parent, p->breadcrumb[0], sizeof shape->id_parent); - } - else { - memcpy(shape->id_parent, attr->id, sizeof shape->id_parent); - } - - scale = nsvg__getAverageScale(attr->xform); - shape->strokeWidth = attr->strokeWidth * scale; - shape->strokeDashOffset = attr->strokeDashOffset * scale; - shape->strokeDashCount = (char)attr->strokeDashCount; - for (i = 0; i < attr->strokeDashCount; i++) - shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; - shape->strokeLineJoin = attr->strokeLineJoin; - shape->strokeLineCap = attr->strokeLineCap; - shape->miterLimit = attr->miterLimit; - shape->fillRule = attr->fillRule; - shape->opacity = attr->opacity; - - shape->paths = p->plist; - p->plist = NULL; - - // Calculate shape bounds - shape->bounds[0] = shape->paths->bounds[0]; - shape->bounds[1] = shape->paths->bounds[1]; - shape->bounds[2] = shape->paths->bounds[2]; - shape->bounds[3] = shape->paths->bounds[3]; - for (path = shape->paths->next; path != NULL; path = path->next) { - shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); - shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); - shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); - shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); - } - - // Set fill - if (attr->hasFill == 0) { - shape->fill.type = NSVG_PAINT_NONE; - } - else if (attr->hasFill == 1) { - shape->fill.type = NSVG_PAINT_COLOR; - shape->fill.color = attr->fillColor; - shape->fill.color |= (unsigned int)(attr->fillOpacity * 255) << 24; - } - else if (attr->hasFill == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->fill.gradient = nsvg__createGradient( - p, attr->fillGradient, localBounds, &shape->fill.type); - if (shape->fill.gradient == NULL) { - shape->fill.type = NSVG_PAINT_NONE; - } - } - - // Set stroke - if (attr->hasStroke == 0) { - shape->stroke.type = NSVG_PAINT_NONE; - } - else if (attr->hasStroke == 1) { - shape->stroke.type = NSVG_PAINT_COLOR; - shape->stroke.color = attr->strokeColor; - shape->stroke.color |= (unsigned int)(attr->strokeOpacity * 255) << 24; - } - else if (attr->hasStroke == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->stroke.gradient = nsvg__createGradient( - p, attr->strokeGradient, localBounds, &shape->stroke.type); - if (shape->stroke.gradient == NULL) - shape->stroke.type = NSVG_PAINT_NONE; - } - - // Set flags - shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00); - - // Add to tail - if (p->image->shapes == NULL) - p->image->shapes = shape; - else - p->shapesTail->next = shape; - p->shapesTail = shape; - - return; - -error: - if (shape) - free(shape); -} - -static void nsvg__addPath(NSVGparser *p, char closed) -{ - NSVGattrib *attr = nsvg__getAttr(p); - NSVGpath *path = NULL; - float bounds[4]; - float *curve; - int i; - - if (p->npts < 4) - return; - - if (closed) - nsvg__lineTo(p, p->pts[0], p->pts[1]); - - // Expect 1 + N*3 points (N = number of cubic bezier segments). - if ((p->npts % 3) != 1) - return; - - path = (NSVGpath *)malloc(sizeof(NSVGpath)); - if (path == NULL) - goto error; - memset(path, 0, sizeof(NSVGpath)); - - path->pts = (float *)malloc(p->npts * 2 * sizeof(float)); - if (path->pts == NULL) - goto error; - path->closed = closed; - path->npts = p->npts; - - // Transform path. - for (i = 0; i < p->npts; ++i) - nsvg__xformPoint( - &path->pts[i * 2], &path->pts[i * 2 + 1], p->pts[i * 2], p->pts[i * 2 + 1], attr->xform); - - // Find bounds - for (i = 0; i < path->npts - 1; i += 3) { - curve = &path->pts[i * 2]; - nsvg__curveBounds(bounds, curve); - if (i == 0) { - path->bounds[0] = bounds[0]; - path->bounds[1] = bounds[1]; - path->bounds[2] = bounds[2]; - path->bounds[3] = bounds[3]; - } - else { - path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); - path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); - path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); - path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); - } - } - - path->next = p->plist; - p->plist = path; - - return; - -error: - if (path != NULL) { - if (path->pts != NULL) - free(path->pts); - free(path); - } -} - -// We roll our own string to float because the std library one uses locale and messes things up. -static double nsvg__atof(const char *s) -{ - char *cur = (char *)s; - char *end = NULL; - double res = 0.0, sign = 1.0; - long long intPart = 0, fracPart = 0; - char hasIntPart = 0, hasFracPart = 0; - - // Parse optional sign - if (*cur == '+') { - cur++; - } - else if (*cur == '-') { - sign = -1; - cur++; - } - - // Parse integer part - if (nsvg__isdigit(*cur)) { - // Parse digit sequence - intPart = strtoll(cur, &end, 10); - if (cur != end) { - res = (double)intPart; - hasIntPart = 1; - cur = end; - } - } - - // Parse fractional part. - if (*cur == '.') { - cur++; // Skip '.' - if (nsvg__isdigit(*cur)) { - // Parse digit sequence - fracPart = strtoll(cur, &end, 10); - if (cur != end) { - res += (double)fracPart / pow(10.0, (double)(end - cur)); - hasFracPart = 1; - cur = end; - } - } - } - - // A valid number should have integer or fractional part. - if (!hasIntPart && !hasFracPart) - return 0.0; - - // Parse optional exponent - if (*cur == 'e' || *cur == 'E') { - long expPart = 0; - cur++; // skip 'E' - expPart = strtol(cur, &end, 10); // Parse digit sequence with sign - if (cur != end) { - res *= pow(10.0, (double)expPart); - } - } - - return res * sign; -} - -static const char *nsvg__parseNumber(const char *s, char *it, const int size) -{ - const int last = size - 1; - int i = 0; - - // sign - if (*s == '-' || *s == '+') { - if (i < last) - it[i++] = *s; - s++; - } - // integer part - while (*s && nsvg__isdigit(*s)) { - if (i < last) - it[i++] = *s; - s++; - } - if (*s == '.') { - // decimal point - if (i < last) - it[i++] = *s; - s++; - // fraction part - while (*s && nsvg__isdigit(*s)) { - if (i < last) - it[i++] = *s; - s++; - } - } - // exponent - if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) { - if (i < last) - it[i++] = *s; - s++; - if (*s == '-' || *s == '+') { - if (i < last) - it[i++] = *s; - s++; - } - while (*s && nsvg__isdigit(*s)) { - if (i < last) - it[i++] = *s; - s++; - } - } - it[i] = '\0'; - - return s; -} - -static const char *nsvg__getNextPathItem(const char *s, char *it, char cmd, int nargs) -{ - it[0] = '\0'; - // Skip white spaces and commas - while (*s && (nsvg__isspace(*s) || *s == ',')) - s++; - if (!*s) - return s; - - /* Blender: Special case for arc command's 4th and 5th arguments. */ - if (ELEM(cmd, 'a', 'A') && ELEM(nargs, 3, 4)) { - it[0] = s[0]; - it[1] = '\0'; - s++; - return s; - } - - if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { - s = nsvg__parseNumber(s, it, 64); - } - else { - // Parse command - it[0] = *s++; - it[1] = '\0'; - return s; - } - - return s; -} - -static unsigned int nsvg__parseColorHex(const char *str) -{ - unsigned int c = 0, r = 0, g = 0, b = 0; - int n = 0; - str++; // skip # - // Calculate number of characters. - while (str[n] && !nsvg__isspace(str[n])) - n++; - if (n == 6) { - sscanf(str, "%x", &c); - } - else if (n == 3) { - sscanf(str, "%x", &c); - c = (c & 0xf) | ((c & 0xf0) << 4) | ((c & 0xf00) << 8); - c |= c << 4; - } - r = (c >> 16) & 0xff; - g = (c >> 8) & 0xff; - b = c & 0xff; - return NSVG_RGB(r, g, b); -} - -static unsigned int nsvg__parseColorRGB(const char *str) -{ - int r = -1, g = -1, b = -1; - char s1[32] = "", s2[32] = ""; - sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b); - if (strchr(s1, '%')) { - return NSVG_RGB((r * 255) / 100, (g * 255) / 100, (b * 255) / 100); - } - else { - return NSVG_RGB(r, g, b); - } -} - -typedef struct NSVGNamedColor { - const char *name; - unsigned int color; -} NSVGNamedColor; - -NSVGNamedColor nsvg__colors[] = { - - {"red", NSVG_RGB(255, 0, 0)}, - {"green", NSVG_RGB(0, 128, 0)}, - {"blue", NSVG_RGB(0, 0, 255)}, - {"yellow", NSVG_RGB(255, 255, 0)}, - {"cyan", NSVG_RGB(0, 255, 255)}, - {"magenta", NSVG_RGB(255, 0, 255)}, - {"black", NSVG_RGB(0, 0, 0)}, - {"grey", NSVG_RGB(128, 128, 128)}, - {"gray", NSVG_RGB(128, 128, 128)}, - {"white", NSVG_RGB(255, 255, 255)}, - -#ifdef NANOSVG_ALL_COLOR_KEYWORDS - {"aliceblue", NSVG_RGB(240, 248, 255)}, - {"antiquewhite", NSVG_RGB(250, 235, 215)}, - {"aqua", NSVG_RGB(0, 255, 255)}, - {"aquamarine", NSVG_RGB(127, 255, 212)}, - {"azure", NSVG_RGB(240, 255, 255)}, - {"beige", NSVG_RGB(245, 245, 220)}, - {"bisque", NSVG_RGB(255, 228, 196)}, - {"blanchedalmond", NSVG_RGB(255, 235, 205)}, - {"blueviolet", NSVG_RGB(138, 43, 226)}, - {"brown", NSVG_RGB(165, 42, 42)}, - {"burlywood", NSVG_RGB(222, 184, 135)}, - {"cadetblue", NSVG_RGB(95, 158, 160)}, - {"chartreuse", NSVG_RGB(127, 255, 0)}, - {"chocolate", NSVG_RGB(210, 105, 30)}, - {"coral", NSVG_RGB(255, 127, 80)}, - {"cornflowerblue", NSVG_RGB(100, 149, 237)}, - {"cornsilk", NSVG_RGB(255, 248, 220)}, - {"crimson", NSVG_RGB(220, 20, 60)}, - {"darkblue", NSVG_RGB(0, 0, 139)}, - {"darkcyan", NSVG_RGB(0, 139, 139)}, - {"darkgoldenrod", NSVG_RGB(184, 134, 11)}, - {"darkgray", NSVG_RGB(169, 169, 169)}, - {"darkgreen", NSVG_RGB(0, 100, 0)}, - {"darkgrey", NSVG_RGB(169, 169, 169)}, - {"darkkhaki", NSVG_RGB(189, 183, 107)}, - {"darkmagenta", NSVG_RGB(139, 0, 139)}, - {"darkolivegreen", NSVG_RGB(85, 107, 47)}, - {"darkorange", NSVG_RGB(255, 140, 0)}, - {"darkorchid", NSVG_RGB(153, 50, 204)}, - {"darkred", NSVG_RGB(139, 0, 0)}, - {"darksalmon", NSVG_RGB(233, 150, 122)}, - {"darkseagreen", NSVG_RGB(143, 188, 143)}, - {"darkslateblue", NSVG_RGB(72, 61, 139)}, - {"darkslategray", NSVG_RGB(47, 79, 79)}, - {"darkslategrey", NSVG_RGB(47, 79, 79)}, - {"darkturquoise", NSVG_RGB(0, 206, 209)}, - {"darkviolet", NSVG_RGB(148, 0, 211)}, - {"deeppink", NSVG_RGB(255, 20, 147)}, - {"deepskyblue", NSVG_RGB(0, 191, 255)}, - {"dimgray", NSVG_RGB(105, 105, 105)}, - {"dimgrey", NSVG_RGB(105, 105, 105)}, - {"dodgerblue", NSVG_RGB(30, 144, 255)}, - {"firebrick", NSVG_RGB(178, 34, 34)}, - {"floralwhite", NSVG_RGB(255, 250, 240)}, - {"forestgreen", NSVG_RGB(34, 139, 34)}, - {"fuchsia", NSVG_RGB(255, 0, 255)}, - {"gainsboro", NSVG_RGB(220, 220, 220)}, - {"ghostwhite", NSVG_RGB(248, 248, 255)}, - {"gold", NSVG_RGB(255, 215, 0)}, - {"goldenrod", NSVG_RGB(218, 165, 32)}, - {"greenyellow", NSVG_RGB(173, 255, 47)}, - {"honeydew", NSVG_RGB(240, 255, 240)}, - {"hotpink", NSVG_RGB(255, 105, 180)}, - {"indianred", NSVG_RGB(205, 92, 92)}, - {"indigo", NSVG_RGB(75, 0, 130)}, - {"ivory", NSVG_RGB(255, 255, 240)}, - {"khaki", NSVG_RGB(240, 230, 140)}, - {"lavender", NSVG_RGB(230, 230, 250)}, - {"lavenderblush", NSVG_RGB(255, 240, 245)}, - {"lawngreen", NSVG_RGB(124, 252, 0)}, - {"lemonchiffon", NSVG_RGB(255, 250, 205)}, - {"lightblue", NSVG_RGB(173, 216, 230)}, - {"lightcoral", NSVG_RGB(240, 128, 128)}, - {"lightcyan", NSVG_RGB(224, 255, 255)}, - {"lightgoldenrodyellow", NSVG_RGB(250, 250, 210)}, - {"lightgray", NSVG_RGB(211, 211, 211)}, - {"lightgreen", NSVG_RGB(144, 238, 144)}, - {"lightgrey", NSVG_RGB(211, 211, 211)}, - {"lightpink", NSVG_RGB(255, 182, 193)}, - {"lightsalmon", NSVG_RGB(255, 160, 122)}, - {"lightseagreen", NSVG_RGB(32, 178, 170)}, - {"lightskyblue", NSVG_RGB(135, 206, 250)}, - {"lightslategray", NSVG_RGB(119, 136, 153)}, - {"lightslategrey", NSVG_RGB(119, 136, 153)}, - {"lightsteelblue", NSVG_RGB(176, 196, 222)}, - {"lightyellow", NSVG_RGB(255, 255, 224)}, - {"lime", NSVG_RGB(0, 255, 0)}, - {"limegreen", NSVG_RGB(50, 205, 50)}, - {"linen", NSVG_RGB(250, 240, 230)}, - {"maroon", NSVG_RGB(128, 0, 0)}, - {"mediumaquamarine", NSVG_RGB(102, 205, 170)}, - {"mediumblue", NSVG_RGB(0, 0, 205)}, - {"mediumorchid", NSVG_RGB(186, 85, 211)}, - {"mediumpurple", NSVG_RGB(147, 112, 219)}, - {"mediumseagreen", NSVG_RGB(60, 179, 113)}, - {"mediumslateblue", NSVG_RGB(123, 104, 238)}, - {"mediumspringgreen", NSVG_RGB(0, 250, 154)}, - {"mediumturquoise", NSVG_RGB(72, 209, 204)}, - {"mediumvioletred", NSVG_RGB(199, 21, 133)}, - {"midnightblue", NSVG_RGB(25, 25, 112)}, - {"mintcream", NSVG_RGB(245, 255, 250)}, - {"mistyrose", NSVG_RGB(255, 228, 225)}, - {"moccasin", NSVG_RGB(255, 228, 181)}, - {"navajowhite", NSVG_RGB(255, 222, 173)}, - {"navy", NSVG_RGB(0, 0, 128)}, - {"oldlace", NSVG_RGB(253, 245, 230)}, - {"olive", NSVG_RGB(128, 128, 0)}, - {"olivedrab", NSVG_RGB(107, 142, 35)}, - {"orange", NSVG_RGB(255, 165, 0)}, - {"orangered", NSVG_RGB(255, 69, 0)}, - {"orchid", NSVG_RGB(218, 112, 214)}, - {"palegoldenrod", NSVG_RGB(238, 232, 170)}, - {"palegreen", NSVG_RGB(152, 251, 152)}, - {"paleturquoise", NSVG_RGB(175, 238, 238)}, - {"palevioletred", NSVG_RGB(219, 112, 147)}, - {"papayawhip", NSVG_RGB(255, 239, 213)}, - {"peachpuff", NSVG_RGB(255, 218, 185)}, - {"peru", NSVG_RGB(205, 133, 63)}, - {"pink", NSVG_RGB(255, 192, 203)}, - {"plum", NSVG_RGB(221, 160, 221)}, - {"powderblue", NSVG_RGB(176, 224, 230)}, - {"purple", NSVG_RGB(128, 0, 128)}, - {"rosybrown", NSVG_RGB(188, 143, 143)}, - {"royalblue", NSVG_RGB(65, 105, 225)}, - {"saddlebrown", NSVG_RGB(139, 69, 19)}, - {"salmon", NSVG_RGB(250, 128, 114)}, - {"sandybrown", NSVG_RGB(244, 164, 96)}, - {"seagreen", NSVG_RGB(46, 139, 87)}, - {"seashell", NSVG_RGB(255, 245, 238)}, - {"sienna", NSVG_RGB(160, 82, 45)}, - {"silver", NSVG_RGB(192, 192, 192)}, - {"skyblue", NSVG_RGB(135, 206, 235)}, - {"slateblue", NSVG_RGB(106, 90, 205)}, - {"slategray", NSVG_RGB(112, 128, 144)}, - {"slategrey", NSVG_RGB(112, 128, 144)}, - {"snow", NSVG_RGB(255, 250, 250)}, - {"springgreen", NSVG_RGB(0, 255, 127)}, - {"steelblue", NSVG_RGB(70, 130, 180)}, - {"tan", NSVG_RGB(210, 180, 140)}, - {"teal", NSVG_RGB(0, 128, 128)}, - {"thistle", NSVG_RGB(216, 191, 216)}, - {"tomato", NSVG_RGB(255, 99, 71)}, - {"turquoise", NSVG_RGB(64, 224, 208)}, - {"violet", NSVG_RGB(238, 130, 238)}, - {"wheat", NSVG_RGB(245, 222, 179)}, - {"whitesmoke", NSVG_RGB(245, 245, 245)}, - {"yellowgreen", NSVG_RGB(154, 205, 50)}, -#endif -}; - -static unsigned int nsvg__parseColorName(const char *str) -{ - int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); - - for (i = 0; i < ncolors; i++) { - if (strcmp(nsvg__colors[i].name, str) == 0) { - return nsvg__colors[i].color; - } - } - - return NSVG_RGB(128, 128, 128); -} - -static unsigned int nsvg__parseColor(const char *str) -{ - size_t len = 0; - while (*str == ' ') - ++str; - len = strlen(str); - if (len >= 1 && *str == '#') - return nsvg__parseColorHex(str); - else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') - return nsvg__parseColorRGB(str); - return nsvg__parseColorName(str); -} - -static float nsvg__parseOpacity(const char *str) -{ - float val = nsvg__atof(str); - if (val < 0.0f) - val = 0.0f; - if (val > 1.0f) - val = 1.0f; - return val; -} - -static float nsvg__parseMiterLimit(const char *str) -{ - float val = nsvg__atof(str); - if (val < 0.0f) - val = 0.0f; - return val; -} - -static int nsvg__parseUnits(const char *units) -{ - if (units[0] == 'p' && units[1] == 'x') - return NSVG_UNITS_PX; - else if (units[0] == 'p' && units[1] == 't') - return NSVG_UNITS_PT; - else if (units[0] == 'p' && units[1] == 'c') - return NSVG_UNITS_PC; - else if (units[0] == 'm' && units[1] == 'm') - return NSVG_UNITS_MM; - else if (units[0] == 'c' && units[1] == 'm') - return NSVG_UNITS_CM; - else if (units[0] == 'i' && units[1] == 'n') - return NSVG_UNITS_IN; - else if (units[0] == '%') - return NSVG_UNITS_PERCENT; - else if (units[0] == 'e' && units[1] == 'm') - return NSVG_UNITS_EM; - else if (units[0] == 'e' && units[1] == 'x') - return NSVG_UNITS_EX; - return NSVG_UNITS_USER; -} - -static int nsvg__isCoordinate(const char *s) -{ - // optional sign - if (*s == '-' || *s == '+') - s++; - // must have at least one digit, or start by a dot - return (nsvg__isdigit(*s) || *s == '.'); -} - -static NSVGcoordinate nsvg__parseCoordinateRaw(const char *str) -{ - NSVGcoordinate coord = {0, NSVG_UNITS_USER}; - char buf[64]; - coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64)); - coord.value = nsvg__atof(buf); - return coord; -} - -static NSVGcoordinate nsvg__coord(float v, int units) -{ - NSVGcoordinate coord = {v, units}; - return coord; -} - -static float nsvg__parseCoordinate(NSVGparser *p, const char *str, float orig, float length) -{ - NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); - return nsvg__convertToPixels(p, coord, orig, length); -} - -static int nsvg__parseTransformArgs(const char *str, float *args, int maxNa, int *na) -{ - const char *end; - const char *ptr; - char it[64]; - - *na = 0; - ptr = str; - while (*ptr && *ptr != '(') - ++ptr; - if (*ptr == 0) - return 1; - end = ptr; - while (*end && *end != ')') - ++end; - if (*end == 0) - return 1; - - while (ptr < end) { - if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { - if (*na >= maxNa) - return 0; - ptr = nsvg__parseNumber(ptr, it, 64); - args[(*na)++] = (float)nsvg__atof(it); - } - else { - ++ptr; - } - } - return (int)(end - str); -} - -static int nsvg__parseMatrix(float *xform, const char *str) -{ - float t[6]; - int na = 0; - int len = nsvg__parseTransformArgs(str, t, 6, &na); - if (na != 6) - return len; - memcpy(xform, t, sizeof(float) * 6); - return len; -} - -static int nsvg__parseTranslate(float *xform, const char *str) -{ - float args[2]; - float t[6]; - int na = 0; - int len = nsvg__parseTransformArgs(str, args, 2, &na); - if (na == 1) - args[1] = 0.0; - - nsvg__xformSetTranslation(t, args[0], args[1]); - memcpy(xform, t, sizeof(float) * 6); - return len; -} - -static int nsvg__parseScale(float *xform, const char *str) -{ - float args[2]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 2, &na); - if (na == 1) - args[1] = args[0]; - nsvg__xformSetScale(t, args[0], args[1]); - memcpy(xform, t, sizeof(float) * 6); - return len; -} - -static int nsvg__parseSkewX(float *xform, const char *str) -{ - float args[1]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 1, &na); - nsvg__xformSetSkewX(t, args[0] / 180.0f * NSVG_PI); - memcpy(xform, t, sizeof(float) * 6); - return len; -} - -static int nsvg__parseSkewY(float *xform, const char *str) -{ - float args[1]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 1, &na); - nsvg__xformSetSkewY(t, args[0] / 180.0f * NSVG_PI); - memcpy(xform, t, sizeof(float) * 6); - return len; -} - -static int nsvg__parseRotate(float *xform, const char *str) -{ - float args[3]; - int na = 0; - float m[6]; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 3, &na); - if (na == 1) - args[1] = args[2] = 0.0f; - nsvg__xformIdentity(m); - - if (na > 1) { - nsvg__xformSetTranslation(t, -args[1], -args[2]); - nsvg__xformMultiply(m, t); - } - - nsvg__xformSetRotation(t, args[0] / 180.0f * NSVG_PI); - nsvg__xformMultiply(m, t); - - if (na > 1) { - nsvg__xformSetTranslation(t, args[1], args[2]); - nsvg__xformMultiply(m, t); - } - - memcpy(xform, m, sizeof(float) * 6); - - return len; -} - -static void nsvg__parseTransform(float *xform, const char *str) -{ - float t[6]; - int len; - nsvg__xformIdentity(xform); - while (*str) { - if (strncmp(str, "matrix", 6) == 0) - len = nsvg__parseMatrix(t, str); - else if (strncmp(str, "translate", 9) == 0) - len = nsvg__parseTranslate(t, str); - else if (strncmp(str, "scale", 5) == 0) - len = nsvg__parseScale(t, str); - else if (strncmp(str, "rotate", 6) == 0) - len = nsvg__parseRotate(t, str); - else if (strncmp(str, "skewX", 5) == 0) - len = nsvg__parseSkewX(t, str); - else if (strncmp(str, "skewY", 5) == 0) - len = nsvg__parseSkewY(t, str); - else { - ++str; - continue; - } - if (len != 0) { - str += len; - } - else { - ++str; - continue; - } - - nsvg__xformPremultiply(xform, t); - } -} - -static void nsvg__parseUrl(char *id, const char *str) -{ - int i = 0; - str += 4; // "url("; - if (*str == '#') - str++; - while (i < 63 && *str != ')') { - id[i] = *str++; - i++; - } - id[i] = '\0'; -} - -static char nsvg__parseLineCap(const char *str) -{ - if (strcmp(str, "butt") == 0) - return NSVG_CAP_BUTT; - else if (strcmp(str, "round") == 0) - return NSVG_CAP_ROUND; - else if (strcmp(str, "square") == 0) - return NSVG_CAP_SQUARE; - // TODO: handle inherit. - return NSVG_CAP_BUTT; -} - -static char nsvg__parseLineJoin(const char *str) -{ - if (strcmp(str, "miter") == 0) - return NSVG_JOIN_MITER; - else if (strcmp(str, "round") == 0) - return NSVG_JOIN_ROUND; - else if (strcmp(str, "bevel") == 0) - return NSVG_JOIN_BEVEL; - // TODO: handle inherit. - return NSVG_JOIN_MITER; -} - -static char nsvg__parseFillRule(const char *str) -{ - if (strcmp(str, "nonzero") == 0) - return NSVG_FILLRULE_NONZERO; - else if (strcmp(str, "evenodd") == 0) - return NSVG_FILLRULE_EVENODD; - // TODO: handle inherit. - return NSVG_FILLRULE_NONZERO; -} - -static const char *nsvg__getNextDashItem(const char *s, char *it) -{ - int n = 0; - it[0] = '\0'; - // Skip white spaces and commas - while (*s && (nsvg__isspace(*s) || *s == ',')) - s++; - // Advance until whitespace, comma or end. - while (*s && (!nsvg__isspace(*s) && *s != ',')) { - if (n < 63) - it[n++] = *s; - s++; - } - it[n++] = '\0'; - return s; -} - -static int nsvg__parseStrokeDashArray(NSVGparser *p, const char *str, float *strokeDashArray) -{ - char item[64]; - int count = 0, i; - float sum = 0.0f; - - // Handle "none" - if (str[0] == 'n') - return 0; - - // Parse dashes - while (*str) { - str = nsvg__getNextDashItem(str, item); - if (!*item) - break; - if (count < NSVG_MAX_DASHES) - strokeDashArray[count++] = fabsf( - nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p))); - } - - for (i = 0; i < count; i++) - sum += strokeDashArray[i]; - if (sum <= 1e-6f) - count = 0; - - return count; -} - -static void nsvg__parseStyle(NSVGparser *p, const char *str); - -static int nsvg__parseAttr(NSVGparser *p, const char *name, const char *value) -{ - float xform[6]; - NSVGattrib *attr = nsvg__getAttr(p); - if (!attr) - return 0; - - if (strcmp(name, "style") == 0) { - nsvg__parseStyle(p, value); - } - else if (strcmp(name, "display") == 0) { - if (strcmp(value, "none") == 0) - attr->visible = 0; - // Don't reset ->visible on display:inline, one display:none hides the whole subtree - } - else if (strcmp(name, "fill") == 0) { - if (strcmp(value, "none") == 0) { - attr->hasFill = 0; - } - else if (strncmp(value, "url(", 4) == 0) { - attr->hasFill = 2; - nsvg__parseUrl(attr->fillGradient, value); - } - else { - attr->hasFill = 1; - attr->fillColor = nsvg__parseColor(value); - } - } - else if (strcmp(name, "opacity") == 0) { - attr->opacity = nsvg__parseOpacity(value); - } - else if (strcmp(name, "fill-opacity") == 0) { - attr->fillOpacity = nsvg__parseOpacity(value); - } - else if (strcmp(name, "stroke") == 0) { - if (strcmp(value, "none") == 0) { - attr->hasStroke = 0; - } - else if (strncmp(value, "url(", 4) == 0) { - attr->hasStroke = 2; - nsvg__parseUrl(attr->strokeGradient, value); - } - else { - attr->hasStroke = 1; - attr->strokeColor = nsvg__parseColor(value); - } - } - else if (strcmp(name, "stroke-width") == 0) { - attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } - else if (strcmp(name, "stroke-dasharray") == 0) { - attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray); - } - else if (strcmp(name, "stroke-dashoffset") == 0) { - attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } - else if (strcmp(name, "stroke-opacity") == 0) { - attr->strokeOpacity = nsvg__parseOpacity(value); - } - else if (strcmp(name, "stroke-linecap") == 0) { - attr->strokeLineCap = nsvg__parseLineCap(value); - } - else if (strcmp(name, "stroke-linejoin") == 0) { - attr->strokeLineJoin = nsvg__parseLineJoin(value); - } - else if (strcmp(name, "stroke-miterlimit") == 0) { - attr->miterLimit = nsvg__parseMiterLimit(value); - } - else if (strcmp(name, "fill-rule") == 0) { - attr->fillRule = nsvg__parseFillRule(value); - } - else if (strcmp(name, "font-size") == 0) { - attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } - else if (strcmp(name, "transform") == 0) { - nsvg__parseTransform(xform, value); - nsvg__xformPremultiply(attr->xform, xform); - } - else if (strcmp(name, "stop-color") == 0) { - attr->stopColor = nsvg__parseColor(value); - } - else if (strcmp(name, "stop-opacity") == 0) { - attr->stopOpacity = nsvg__parseOpacity(value); - } - else if (strcmp(name, "offset") == 0) { - attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f); - } - else if (strcmp(name, "id") == 0) { - strncpy(attr->id, value, 63); - attr->id[63] = '\0'; - } - else { - return 0; - } - return 1; -} - -static int nsvg__parseNameValue(NSVGparser *p, const char *start, const char *end) -{ - const char *str; - const char *val; - char name[512]; - char value[512]; - int n; - - str = start; - while (str < end && *str != ':') - ++str; - - val = str; - - // Right Trim - while (str > start && (*str == ':' || nsvg__isspace(*str))) - --str; - ++str; - - n = (int)(str - start); - if (n > 511) - n = 511; - if (n) - memcpy(name, start, n); - name[n] = 0; - - while (val < end && (*val == ':' || nsvg__isspace(*val))) - ++val; - - n = (int)(end - val); - if (n > 511) - n = 511; - if (n) - memcpy(value, val, n); - value[n] = 0; - - return nsvg__parseAttr(p, name, value); -} - -static void nsvg__parseStyle(NSVGparser *p, const char *str) -{ - const char *start; - const char *end; - - while (*str) { - // Left Trim - while (*str && nsvg__isspace(*str)) - ++str; - start = str; - while (*str && *str != ';') - ++str; - end = str; - - // Right Trim - while (end > start && (*end == ';' || nsvg__isspace(*end))) - --end; - ++end; - - nsvg__parseNameValue(p, start, end); - if (*str) - ++str; - } -} - -static void nsvg__parseAttribs(NSVGparser *p, const char **attr) -{ - int i; - for (i = 0; attr[i]; i += 2) { - if (strcmp(attr[i], "style") == 0) - nsvg__parseStyle(p, attr[i + 1]); - else - nsvg__parseAttr(p, attr[i], attr[i + 1]); - } -} - -static int nsvg__getArgsPerElement(char cmd) -{ - switch (cmd) { - case 'v': - case 'V': - case 'h': - case 'H': - return 1; - case 'm': - case 'M': - case 'l': - case 'L': - case 't': - case 'T': - return 2; - case 'q': - case 'Q': - case 's': - case 'S': - return 4; - case 'c': - case 'C': - return 6; - case 'a': - case 'A': - return 7; - case 'z': - case 'Z': - return 0; - } - return -1; -} - -static void nsvg__pathMoveTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) -{ - if (rel) { - *cpx += args[0]; - *cpy += args[1]; - } - else { - *cpx = args[0]; - *cpy = args[1]; - } - nsvg__moveTo(p, *cpx, *cpy); -} - -static void nsvg__pathLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) -{ - if (rel) { - *cpx += args[0]; - *cpy += args[1]; - } - else { - *cpx = args[0]; - *cpy = args[1]; - } - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathHLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) -{ - if (rel) - *cpx += args[0]; - else - *cpx = args[0]; - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathVLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) -{ - if (rel) - *cpy += args[0]; - else - *cpy = args[0]; - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathCubicBezTo( - NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) -{ - float x2, y2, cx1, cy1, cx2, cy2; - - if (rel) { - cx1 = *cpx + args[0]; - cy1 = *cpy + args[1]; - cx2 = *cpx + args[2]; - cy2 = *cpy + args[3]; - x2 = *cpx + args[4]; - y2 = *cpy + args[5]; - } - else { - cx1 = args[0]; - cy1 = args[1]; - cx2 = args[2]; - cy2 = args[3]; - x2 = args[4]; - y2 = args[5]; - } - - nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); - - *cpx2 = cx2; - *cpy2 = cy2; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathCubicBezShortTo( - NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) -{ - float x1, y1, x2, y2, cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - cx2 = *cpx + args[0]; - cy2 = *cpy + args[1]; - x2 = *cpx + args[2]; - y2 = *cpy + args[3]; - } - else { - cx2 = args[0]; - cy2 = args[1]; - x2 = args[2]; - y2 = args[3]; - } - - cx1 = 2 * x1 - *cpx2; - cy1 = 2 * y1 - *cpy2; - - nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); - - *cpx2 = cx2; - *cpy2 = cy2; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathQuadBezTo( - NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) -{ - float x1, y1, x2, y2, cx, cy; - float cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - cx = *cpx + args[0]; - cy = *cpy + args[1]; - x2 = *cpx + args[2]; - y2 = *cpy + args[3]; - } - else { - cx = args[0]; - cy = args[1]; - x2 = args[2]; - y2 = args[3]; - } - - // Convert to cubic bezier - cx1 = x1 + 2.0f / 3.0f * (cx - x1); - cy1 = y1 + 2.0f / 3.0f * (cy - y1); - cx2 = x2 + 2.0f / 3.0f * (cx - x2); - cy2 = y2 + 2.0f / 3.0f * (cy - y2); - - nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); - - *cpx2 = cx; - *cpy2 = cy; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathQuadBezShortTo( - NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) -{ - float x1, y1, x2, y2, cx, cy; - float cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - x2 = *cpx + args[0]; - y2 = *cpy + args[1]; - } - else { - x2 = args[0]; - y2 = args[1]; - } - - cx = 2 * x1 - *cpx2; - cy = 2 * y1 - *cpy2; - - // Convert to cubix bezier - cx1 = x1 + 2.0f / 3.0f * (cx - x1); - cy1 = y1 + 2.0f / 3.0f * (cy - y1); - cx2 = x2 + 2.0f / 3.0f * (cx - x2); - cy2 = y2 + 2.0f / 3.0f * (cy - y2); - - nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); - - *cpx2 = cx; - *cpy2 = cy; - *cpx = x2; - *cpy = y2; -} - -static float nsvg__sqr(float x) -{ - return x * x; -} -static float nsvg__vmag(float x, float y) -{ - return sqrtf(x * x + y * y); -} - -static float nsvg__vecrat(float ux, float uy, float vx, float vy) -{ - return (ux * vx + uy * vy) / (nsvg__vmag(ux, uy) * nsvg__vmag(vx, vy)); -} - -static float nsvg__vecang(float ux, float uy, float vx, float vy) -{ - float r = nsvg__vecrat(ux, uy, vx, vy); - if (r < -1.0f) - r = -1.0f; - if (r > 1.0f) - r = 1.0f; - return ((ux * vy < uy * vx) ? -1.0f : 1.0f) * acosf(r); -} - -static void nsvg__pathArcTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) -{ - // Ported from canvg (https://code.google.com/p/canvg/) - float rx, ry, rotx; - float x1, y1, x2, y2, cx, cy, dx, dy, d; - float x1p, y1p, cxp, cyp, s, sa, sb; - float ux, uy, vx, vy, a1, da; - float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; - float sinrx, cosrx; - int fa, fs; - int i, ndivs; - float hda, kappa; - - rx = fabsf(args[0]); // y radius - ry = fabsf(args[1]); // x radius - rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle - fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc - fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction - x1 = *cpx; // start point - y1 = *cpy; - if (rel) { // end point - x2 = *cpx + args[5]; - y2 = *cpy + args[6]; - } - else { - x2 = args[5]; - y2 = args[6]; - } - - dx = x1 - x2; - dy = y1 - y2; - d = sqrtf(dx * dx + dy * dy); - if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { - // The arc degenerates to a line - nsvg__lineTo(p, x2, y2); - *cpx = x2; - *cpy = y2; - return; - } - - sinrx = sinf(rotx); - cosrx = cosf(rotx); - - // Convert to center point parameterization. - // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes - // 1) Compute x1', y1' - x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; - y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; - d = nsvg__sqr(x1p) / nsvg__sqr(rx) + nsvg__sqr(y1p) / nsvg__sqr(ry); - if (d > 1) { - d = sqrtf(d); - rx *= d; - ry *= d; - } - // 2) Compute cx', cy' - s = 0.0f; - sa = nsvg__sqr(rx) * nsvg__sqr(ry) - nsvg__sqr(rx) * nsvg__sqr(y1p) - - nsvg__sqr(ry) * nsvg__sqr(x1p); - sb = nsvg__sqr(rx) * nsvg__sqr(y1p) + nsvg__sqr(ry) * nsvg__sqr(x1p); - if (sa < 0.0f) - sa = 0.0f; - if (sb > 0.0f) - s = sqrtf(sa / sb); - if (fa == fs) - s = -s; - cxp = s * rx * y1p / ry; - cyp = s * -ry * x1p / rx; - - // 3) Compute cx,cy from cx',cy' - cx = (x1 + x2) / 2.0f + cosrx * cxp - sinrx * cyp; - cy = (y1 + y2) / 2.0f + sinrx * cxp + cosrx * cyp; - - // 4) Calculate theta1, and delta theta. - ux = (x1p - cxp) / rx; - uy = (y1p - cyp) / ry; - vx = (-x1p - cxp) / rx; - vy = (-y1p - cyp) / ry; - a1 = nsvg__vecang(1.0f, 0.0f, ux, uy); // Initial angle - da = nsvg__vecang(ux, uy, vx, vy); // Delta angle - - // if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; - // if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; - - if (fs == 0 && da > 0) - da -= 2 * NSVG_PI; - else if (fs == 1 && da < 0) - da += 2 * NSVG_PI; - - // Approximate the arc using cubic spline segments. - t[0] = cosrx; - t[1] = sinrx; - t[2] = -sinrx; - t[3] = cosrx; - t[4] = cx; - t[5] = cy; - - // Split arc into max 90 degree segments. - // The loop assumes an iteration per end point (including start and end), this +1. - ndivs = (int)(fabsf(da) / (NSVG_PI * 0.5f) + 1.0f); - hda = (da / (float)ndivs) / 2.0f; - // Fix for ticket #179: division by 0: avoid cotangens around 0 (infinite) - if ((hda < 1e-3f) && (hda > -1e-3f)) - hda *= 0.5f; - else - hda = (1.0f - cosf(hda)) / sinf(hda); - kappa = fabsf(4.0f / 3.0f * hda); - if (da < 0.0f) - kappa = -kappa; - - for (i = 0; i <= ndivs; i++) { - a = a1 + da * ((float)i / (float)ndivs); - dx = cosf(a); - dy = sinf(a); - nsvg__xformPoint(&x, &y, dx * rx, dy * ry, t); // position - nsvg__xformVec(&tanx, &tany, -dy * rx * kappa, dx * ry * kappa, t); // tangent - if (i > 0) - nsvg__cubicBezTo(p, px + ptanx, py + ptany, x - tanx, y - tany, x, y); - px = x; - py = y; - ptanx = tanx; - ptany = tany; - } - - *cpx = x2; - *cpy = y2; -} - -static void nsvg__parsePath(NSVGparser *p, const char **attr) -{ - const char *s = NULL; - char cmd = '\0'; - float args[10]; - int nargs; - int rargs = 0; - char initPoint; - float cpx, cpy, cpx2, cpy2; - const char *tmp[4]; - char closedFlag; - int i; - char item[64]; - - for (i = 0; attr[i]; i += 2) { - if (strcmp(attr[i], "d") == 0) { - s = attr[i + 1]; - } - else { - tmp[0] = attr[i]; - tmp[1] = attr[i + 1]; - tmp[2] = 0; - tmp[3] = 0; - nsvg__parseAttribs(p, tmp); - } - } - - if (s) { - nsvg__resetPath(p); - cpx = 0; - cpy = 0; - cpx2 = 0; - cpy2 = 0; - initPoint = 0; - closedFlag = 0; - nargs = 0; - - while (*s) { - s = nsvg__getNextPathItem(s, item, cmd, nargs); - if (!*item) - break; - if (cmd != '\0' && nsvg__isCoordinate(item)) { - if (nargs < 10) - args[nargs++] = (float)nsvg__atof(item); - if (nargs >= rargs) { - switch (cmd) { - case 'm': - case 'M': - nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); - // Moveto can be followed by multiple coordinate pairs, - // which should be treated as linetos. - cmd = (cmd == 'm') ? 'l' : 'L'; - rargs = nsvg__getArgsPerElement(cmd); - cpx2 = cpx; - cpy2 = cpy; - initPoint = 1; - break; - case 'l': - case 'L': - nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); - cpx2 = cpx; - cpy2 = cpy; - break; - case 'H': - case 'h': - nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); - cpx2 = cpx; - cpy2 = cpy; - break; - case 'V': - case 'v': - nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); - cpx2 = cpx; - cpy2 = cpy; - break; - case 'C': - case 'c': - nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); - break; - case 'S': - case 's': - nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); - break; - case 'Q': - case 'q': - nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); - break; - case 'T': - case 't': - nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); - break; - case 'A': - case 'a': - nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); - cpx2 = cpx; - cpy2 = cpy; - break; - default: - if (nargs >= 2) { - cpx = args[nargs - 2]; - cpy = args[nargs - 1]; - cpx2 = cpx; - cpy2 = cpy; - } - break; - } - nargs = 0; - } - } - else { - cmd = item[0]; - if (cmd == 'M' || cmd == 'm') { - // Commit path. - if (p->npts > 0) - nsvg__addPath(p, closedFlag); - // Start new subpath. - nsvg__resetPath(p); - closedFlag = 0; - nargs = 0; - } - else if (initPoint == 0) { - // Do not allow other commands until initial point has been set (moveTo called once). - cmd = '\0'; - } - if (cmd == 'Z' || cmd == 'z') { - closedFlag = 1; - // Commit path. - if (p->npts > 0) { - // Move current point to first point - cpx = p->pts[0]; - cpy = p->pts[1]; - cpx2 = cpx; - cpy2 = cpy; - nsvg__addPath(p, closedFlag); - } - // Start new subpath. - nsvg__resetPath(p); - nsvg__moveTo(p, cpx, cpy); - closedFlag = 0; - nargs = 0; - } - rargs = nsvg__getArgsPerElement(cmd); - if (rargs == -1) { - // Command not recognized - cmd = '\0'; - rargs = 0; - } - } - } - // Commit path. - if (p->npts) - nsvg__addPath(p, closedFlag); - } - - nsvg__addShape(p); -} - -static void nsvg__parseRect(NSVGparser *p, const char **attr) -{ - float x = 0.0f; - float y = 0.0f; - float w = 0.0f; - float h = 0.0f; - float rx = -1.0f; // marks not set - float ry = -1.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "x") == 0) - x = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y") == 0) - y = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "width") == 0) - w = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p)); - if (strcmp(attr[i], "height") == 0) - h = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p)); - if (strcmp(attr[i], "rx") == 0) - rx = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p))); - if (strcmp(attr[i], "ry") == 0) - ry = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p))); - } - } - - if (rx < 0.0f && ry > 0.0f) - rx = ry; - if (ry < 0.0f && rx > 0.0f) - ry = rx; - if (rx < 0.0f) - rx = 0.0f; - if (ry < 0.0f) - ry = 0.0f; - if (rx > w / 2.0f) - rx = w / 2.0f; - if (ry > h / 2.0f) - ry = h / 2.0f; - - if (w != 0.0f && h != 0.0f) { - nsvg__resetPath(p); - - if (rx < 0.00001f || ry < 0.0001f) { - nsvg__moveTo(p, x, y); - nsvg__lineTo(p, x + w, y); - nsvg__lineTo(p, x + w, y + h); - nsvg__lineTo(p, x, y + h); - } - else { - // Rounded rectangle - nsvg__moveTo(p, x + rx, y); - nsvg__lineTo(p, x + w - rx, y); - nsvg__cubicBezTo(p, - x + w - rx * (1 - NSVG_KAPPA90), - y, - x + w, - y + ry * (1 - NSVG_KAPPA90), - x + w, - y + ry); - nsvg__lineTo(p, x + w, y + h - ry); - nsvg__cubicBezTo(p, - x + w, - y + h - ry * (1 - NSVG_KAPPA90), - x + w - rx * (1 - NSVG_KAPPA90), - y + h, - x + w - rx, - y + h); - nsvg__lineTo(p, x + rx, y + h); - nsvg__cubicBezTo(p, - x + rx * (1 - NSVG_KAPPA90), - y + h, - x, - y + h - ry * (1 - NSVG_KAPPA90), - x, - y + h - ry); - nsvg__lineTo(p, x, y + ry); - nsvg__cubicBezTo( - p, x, y + ry * (1 - NSVG_KAPPA90), x + rx * (1 - NSVG_KAPPA90), y, x + rx, y); - } - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseCircle(NSVGparser *p, const char **attr) -{ - float cx = 0.0f; - float cy = 0.0f; - float r = 0.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "cx") == 0) - cx = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "cy") == 0) - cy = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "r") == 0) - r = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualLength(p))); - } - } - - if (r > 0.0f) { - nsvg__resetPath(p); - - nsvg__moveTo(p, cx + r, cy); - nsvg__cubicBezTo(p, cx + r, cy + r * NSVG_KAPPA90, cx + r * NSVG_KAPPA90, cy + r, cx, cy + r); - nsvg__cubicBezTo(p, cx - r * NSVG_KAPPA90, cy + r, cx - r, cy + r * NSVG_KAPPA90, cx - r, cy); - nsvg__cubicBezTo(p, cx - r, cy - r * NSVG_KAPPA90, cx - r * NSVG_KAPPA90, cy - r, cx, cy - r); - nsvg__cubicBezTo(p, cx + r * NSVG_KAPPA90, cy - r, cx + r, cy - r * NSVG_KAPPA90, cx + r, cy); - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseEllipse(NSVGparser *p, const char **attr) -{ - float cx = 0.0f; - float cy = 0.0f; - float rx = 0.0f; - float ry = 0.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "cx") == 0) - cx = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "cy") == 0) - cy = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "rx") == 0) - rx = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p))); - if (strcmp(attr[i], "ry") == 0) - ry = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p))); - } - } - - if (rx > 0.0f && ry > 0.0f) { - - nsvg__resetPath(p); - - nsvg__moveTo(p, cx + rx, cy); - nsvg__cubicBezTo( - p, cx + rx, cy + ry * NSVG_KAPPA90, cx + rx * NSVG_KAPPA90, cy + ry, cx, cy + ry); - nsvg__cubicBezTo( - p, cx - rx * NSVG_KAPPA90, cy + ry, cx - rx, cy + ry * NSVG_KAPPA90, cx - rx, cy); - nsvg__cubicBezTo( - p, cx - rx, cy - ry * NSVG_KAPPA90, cx - rx * NSVG_KAPPA90, cy - ry, cx, cy - ry); - nsvg__cubicBezTo( - p, cx + rx * NSVG_KAPPA90, cy - ry, cx + rx, cy - ry * NSVG_KAPPA90, cx + rx, cy); - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseLine(NSVGparser *p, const char **attr) -{ - float x1 = 0.0; - float y1 = 0.0; - float x2 = 0.0; - float y2 = 0.0; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "x1") == 0) - x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y1") == 0) - y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "x2") == 0) - x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y2") == 0) - y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - } - } - - nsvg__resetPath(p); - - nsvg__moveTo(p, x1, y1); - nsvg__lineTo(p, x2, y2); - - nsvg__addPath(p, 0); - - nsvg__addShape(p); -} - -static void nsvg__parsePoly(NSVGparser *p, const char **attr, int closeFlag) -{ - int i; - const char *s; - float args[2]; - int nargs, npts = 0; - char item[64]; - - nsvg__resetPath(p); - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "points") == 0) { - s = attr[i + 1]; - nargs = 0; - while (*s) { - s = nsvg__getNextPathItem(s, item, '\0', nargs); - args[nargs++] = (float)nsvg__atof(item); - if (nargs >= 2) { - if (npts == 0) - nsvg__moveTo(p, args[0], args[1]); - else - nsvg__lineTo(p, args[0], args[1]); - nargs = 0; - npts++; - } - } - } - } - } - - nsvg__addPath(p, (char)closeFlag); - - nsvg__addShape(p); -} - -static void nsvg__parseSVG(NSVGparser *p, const char **attr) -{ - int i; - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "width") == 0) { - p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); - } - else if (strcmp(attr[i], "height") == 0) { - p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); - } - else if (strcmp(attr[i], "viewBox") == 0) { - const char *s = attr[i + 1]; - char buf[64]; - s = nsvg__parseNumber(s, buf, 64); - p->viewMinx = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) - s++; - if (!*s) - return; - s = nsvg__parseNumber(s, buf, 64); - p->viewMiny = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) - s++; - if (!*s) - return; - s = nsvg__parseNumber(s, buf, 64); - p->viewWidth = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) - s++; - if (!*s) - return; - s = nsvg__parseNumber(s, buf, 64); - p->viewHeight = nsvg__atof(buf); - } - else if (strcmp(attr[i], "preserveAspectRatio") == 0) { - if (strstr(attr[i + 1], "none") != 0) { - // No uniform scaling - p->alignType = NSVG_ALIGN_NONE; - } - else { - // Parse X align - if (strstr(attr[i + 1], "xMin") != 0) - p->alignX = NSVG_ALIGN_MIN; - else if (strstr(attr[i + 1], "xMid") != 0) - p->alignX = NSVG_ALIGN_MID; - else if (strstr(attr[i + 1], "xMax") != 0) - p->alignX = NSVG_ALIGN_MAX; - // Parse X align - if (strstr(attr[i + 1], "yMin") != 0) - p->alignY = NSVG_ALIGN_MIN; - else if (strstr(attr[i + 1], "yMid") != 0) - p->alignY = NSVG_ALIGN_MID; - else if (strstr(attr[i + 1], "yMax") != 0) - p->alignY = NSVG_ALIGN_MAX; - // Parse meet/slice - p->alignType = NSVG_ALIGN_MEET; - if (strstr(attr[i + 1], "slice") != 0) - p->alignType = NSVG_ALIGN_SLICE; - } - } - } - } -} - -static void nsvg__parseGradient(NSVGparser *p, const char **attr, char type) -{ - int i; - NSVGgradientData *grad = (NSVGgradientData *)malloc(sizeof(NSVGgradientData)); - if (grad == NULL) - return; - memset(grad, 0, sizeof(NSVGgradientData)); - grad->units = NSVG_OBJECT_SPACE; - grad->type = type; - if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) { - grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT); - grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - } - else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) { - grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - } - - nsvg__xformIdentity(grad->xform); - - for (i = 0; attr[i]; i += 2) { - if (strcmp(attr[i], "id") == 0) { - strncpy(grad->id, attr[i + 1], 63); - grad->id[63] = '\0'; - } - else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "gradientUnits") == 0) { - if (strcmp(attr[i + 1], "objectBoundingBox") == 0) - grad->units = NSVG_OBJECT_SPACE; - else - grad->units = NSVG_USER_SPACE; - } - else if (strcmp(attr[i], "gradientTransform") == 0) { - nsvg__parseTransform(grad->xform, attr[i + 1]); - } - else if (strcmp(attr[i], "cx") == 0) { - grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]); - } - else if (strcmp(attr[i], "cy") == 0) { - grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]); - } - else if (strcmp(attr[i], "r") == 0) { - grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]); - } - else if (strcmp(attr[i], "fx") == 0) { - grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]); - } - else if (strcmp(attr[i], "fy") == 0) { - grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]); - } - else if (strcmp(attr[i], "x1") == 0) { - grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]); - } - else if (strcmp(attr[i], "y1") == 0) { - grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]); - } - else if (strcmp(attr[i], "x2") == 0) { - grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]); - } - else if (strcmp(attr[i], "y2") == 0) { - grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]); - } - else if (strcmp(attr[i], "spreadMethod") == 0) { - if (strcmp(attr[i + 1], "pad") == 0) - grad->spread = NSVG_SPREAD_PAD; - else if (strcmp(attr[i + 1], "reflect") == 0) - grad->spread = NSVG_SPREAD_REFLECT; - else if (strcmp(attr[i + 1], "repeat") == 0) - grad->spread = NSVG_SPREAD_REPEAT; - } - else if (strcmp(attr[i], "xlink:href") == 0) { - const char *href = attr[i + 1]; - strncpy(grad->ref, href + 1, 62); - grad->ref[62] = '\0'; - } - } - } - - grad->next = p->gradients; - p->gradients = grad; -} - -static void nsvg__parseGradientStop(NSVGparser *p, const char **attr) -{ - NSVGattrib *curAttr = nsvg__getAttr(p); - NSVGgradientData *grad; - NSVGgradientStop *stop; - int i, idx; - - curAttr->stopOffset = 0; - curAttr->stopColor = 0; - curAttr->stopOpacity = 1.0f; - - for (i = 0; attr[i]; i += 2) { - nsvg__parseAttr(p, attr[i], attr[i + 1]); - } - - // Add stop to the last gradient. - grad = p->gradients; - if (grad == NULL) - return; - - grad->nstops++; - grad->stops = (NSVGgradientStop *)realloc(grad->stops, sizeof(NSVGgradientStop) * grad->nstops); - if (grad->stops == NULL) - return; - - // Insert - idx = grad->nstops - 1; - for (i = 0; i < grad->nstops - 1; i++) { - if (curAttr->stopOffset < grad->stops[i].offset) { - idx = i; - break; - } - } - if (idx != grad->nstops - 1) { - for (i = grad->nstops - 1; i > idx; i--) - grad->stops[i] = grad->stops[i - 1]; - } - - stop = &grad->stops[idx]; - stop->color = curAttr->stopColor; - stop->color |= (unsigned int)(curAttr->stopOpacity * 255) << 24; - stop->offset = curAttr->stopOffset; -} - -static void nsvg__startElement(void *ud, const char *el, const char **attr) -{ - NSVGparser *p = (NSVGparser *)ud; - - if (p->defsFlag) { - // Skip everything but gradients in defs - if (strcmp(el, "linearGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); - } - else if (strcmp(el, "radialGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); - } - else if (strcmp(el, "stop") == 0) { - nsvg__parseGradientStop(p, attr); - } - return; - } - - if (strcmp(el, "g") == 0) { - nsvg__pushAttr(p); - nsvg__parseAttribs(p, attr); - - /* Save the breadcrumb of groups. */ - if (p->breadcrumb_len < NSVG_MAX_BREADCRUMB) { - NSVGattrib *attr_id = nsvg__getAttr(p); - memcpy( - p->breadcrumb[p->breadcrumb_len], attr_id->id, sizeof(p->breadcrumb[p->breadcrumb_len])); - p->breadcrumb_len++; - } - } - else if (strcmp(el, "path") == 0) { - if (p->pathFlag) // Do not allow nested paths. - return; - nsvg__pushAttr(p); - nsvg__parsePath(p, attr); - nsvg__popAttr(p); - } - else if (strcmp(el, "rect") == 0) { - nsvg__pushAttr(p); - nsvg__parseRect(p, attr); - nsvg__popAttr(p); - } - else if (strcmp(el, "circle") == 0) { - nsvg__pushAttr(p); - nsvg__parseCircle(p, attr); - nsvg__popAttr(p); - } - else if (strcmp(el, "ellipse") == 0) { - nsvg__pushAttr(p); - nsvg__parseEllipse(p, attr); - nsvg__popAttr(p); - } - else if (strcmp(el, "line") == 0) { - nsvg__pushAttr(p); - nsvg__parseLine(p, attr); - nsvg__popAttr(p); - } - else if (strcmp(el, "polyline") == 0) { - nsvg__pushAttr(p); - nsvg__parsePoly(p, attr, 0); - nsvg__popAttr(p); - } - else if (strcmp(el, "polygon") == 0) { - nsvg__pushAttr(p); - nsvg__parsePoly(p, attr, 1); - nsvg__popAttr(p); - } - else if (strcmp(el, "linearGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); - } - else if (strcmp(el, "radialGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); - } - else if (strcmp(el, "stop") == 0) { - nsvg__parseGradientStop(p, attr); - } - else if (strcmp(el, "defs") == 0) { - p->defsFlag = 1; - } - else if (strcmp(el, "svg") == 0) { - nsvg__parseSVG(p, attr); - } -} - -static void nsvg__endElement(void *ud, const char *el) -{ - NSVGparser *p = (NSVGparser *)ud; - - if (strcmp(el, "g") == 0) { - /* Remove the breadcrumb level. */ - if (p->breadcrumb_len > 0) { - p->breadcrumb[p->breadcrumb_len - 1][0] = '\0'; - p->breadcrumb_len--; - } - - nsvg__popAttr(p); - } - else if (strcmp(el, "path") == 0) { - p->pathFlag = 0; - } - else if (strcmp(el, "defs") == 0) { - p->defsFlag = 0; - } -} - -static void nsvg__content(void *ud, const char *s) -{ - NSVG_NOTUSED(ud); - NSVG_NOTUSED(s); - // empty -} - -static void nsvg__imageBounds(NSVGparser *p, float *bounds) -{ - NSVGshape *shape; - shape = p->image->shapes; - if (shape == NULL) { - bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; - return; - } - bounds[0] = shape->bounds[0]; - bounds[1] = shape->bounds[1]; - bounds[2] = shape->bounds[2]; - bounds[3] = shape->bounds[3]; - for (shape = shape->next; shape != NULL; shape = shape->next) { - bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); - bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); - bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); - bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); - } -} - -static float nsvg__viewAlign(float content, float container, int type) -{ - if (type == NSVG_ALIGN_MIN) - return 0; - else if (type == NSVG_ALIGN_MAX) - return container - content; - // mid - return (container - content) * 0.5f; -} - -static void nsvg__scaleGradient(NSVGgradient *grad, float tx, float ty, float sx, float sy) -{ - float t[6]; - nsvg__xformSetTranslation(t, tx, ty); - nsvg__xformMultiply(grad->xform, t); - - nsvg__xformSetScale(t, sx, sy); - nsvg__xformMultiply(grad->xform, t); -} - -static void nsvg__scaleToViewbox(NSVGparser *p, const char *units) -{ - NSVGshape *shape; - NSVGpath *path; - float tx, ty, sx, sy, us, bounds[4], t[6], avgs; - int i; - float *pt; - - // Guess image size if not set completely. - nsvg__imageBounds(p, bounds); - - if (p->viewWidth == 0) { - if (p->image->width > 0) { - p->viewWidth = p->image->width; - } - else { - p->viewMinx = bounds[0]; - p->viewWidth = bounds[2] - bounds[0]; - } - } - if (p->viewHeight == 0) { - if (p->image->height > 0) { - p->viewHeight = p->image->height; - } - else { - p->viewMiny = bounds[1]; - p->viewHeight = bounds[3] - bounds[1]; - } - } - if (p->image->width == 0) - p->image->width = p->viewWidth; - if (p->image->height == 0) - p->image->height = p->viewHeight; - - tx = -p->viewMinx; - ty = -p->viewMiny; - sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; - sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; - // Unit scaling - us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); - - // Fix aspect ratio - if (p->alignType == NSVG_ALIGN_MEET) { - // fit whole image into viewbox - sx = sy = nsvg__minf(sx, sy); - tx += nsvg__viewAlign(p->viewWidth * sx, p->image->width, p->alignX) / sx; - ty += nsvg__viewAlign(p->viewHeight * sy, p->image->height, p->alignY) / sy; - } - else if (p->alignType == NSVG_ALIGN_SLICE) { - // fill whole viewbox with image - sx = sy = nsvg__maxf(sx, sy); - tx += nsvg__viewAlign(p->viewWidth * sx, p->image->width, p->alignX) / sx; - ty += nsvg__viewAlign(p->viewHeight * sy, p->image->height, p->alignY) / sy; - } - - // Transform - sx *= us; - sy *= us; - avgs = (sx + sy) / 2.0f; - for (shape = p->image->shapes; shape != NULL; shape = shape->next) { - shape->bounds[0] = (shape->bounds[0] + tx) * sx; - shape->bounds[1] = (shape->bounds[1] + ty) * sy; - shape->bounds[2] = (shape->bounds[2] + tx) * sx; - shape->bounds[3] = (shape->bounds[3] + ty) * sy; - for (path = shape->paths; path != NULL; path = path->next) { - path->bounds[0] = (path->bounds[0] + tx) * sx; - path->bounds[1] = (path->bounds[1] + ty) * sy; - path->bounds[2] = (path->bounds[2] + tx) * sx; - path->bounds[3] = (path->bounds[3] + ty) * sy; - for (i = 0; i < path->npts; i++) { - pt = &path->pts[i * 2]; - pt[0] = (pt[0] + tx) * sx; - pt[1] = (pt[1] + ty) * sy; - } - } - - if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || - shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { - nsvg__scaleGradient(shape->fill.gradient, tx, ty, sx, sy); - memcpy(t, shape->fill.gradient->xform, sizeof(float) * 6); - nsvg__xformInverse(shape->fill.gradient->xform, t); - } - if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || - shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { - nsvg__scaleGradient(shape->stroke.gradient, tx, ty, sx, sy); - memcpy(t, shape->stroke.gradient->xform, sizeof(float) * 6); - nsvg__xformInverse(shape->stroke.gradient->xform, t); - } - - shape->strokeWidth *= avgs; - shape->strokeDashOffset *= avgs; - for (i = 0; i < shape->strokeDashCount; i++) - shape->strokeDashArray[i] *= avgs; - } -} - -NSVGimage *nsvgParse(char *input, const char *units, float dpi) -{ - NSVGparser *p; - NSVGimage *ret = 0; - - p = nsvg__createParser(); - if (p == NULL) { - return NULL; - } - p->dpi = dpi; - - nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); - - // Scale to viewBox - nsvg__scaleToViewbox(p, units); - - ret = p->image; - p->image = NULL; - - nsvg__deleteParser(p); - - return ret; -} - -NSVGimage *nsvgParseFromFile(const char *filename, const char *units, float dpi) -{ - FILE *fp = NULL; - size_t size; - char *data = NULL; - NSVGimage *image = NULL; - - fp = fopen(filename, "rb"); - if (!fp) - goto error; - fseek(fp, 0, SEEK_END); - size = ftell(fp); - fseek(fp, 0, SEEK_SET); - data = (char *)malloc(size + 1); - if (data == NULL) - goto error; - if (fread(data, 1, size, fp) != size) - goto error; - data[size] = '\0'; // Must be null terminated. - fclose(fp); - image = nsvgParse(data, units, dpi); - free(data); - - return image; - -error: - if (fp) - fclose(fp); - if (data) - free(data); - if (image) - nsvgDelete(image); - return NULL; -} - -NSVGpath *nsvgDuplicatePath(NSVGpath *p) -{ - NSVGpath *res = NULL; - - if (p == NULL) - return NULL; - - res = (NSVGpath *)malloc(sizeof(NSVGpath)); - if (res == NULL) - goto error; - memset(res, 0, sizeof(NSVGpath)); - - res->pts = (float *)malloc(p->npts * 2 * sizeof(float)); - if (res->pts == NULL) - goto error; - memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2); - res->npts = p->npts; - - memcpy(res->bounds, p->bounds, sizeof(p->bounds)); - - res->closed = p->closed; - - return res; - -error: - if (res != NULL) { - free(res->pts); - free(res); - } - return NULL; -} - -void nsvgDelete(NSVGimage *image) -{ - NSVGshape *snext, *shape; - if (image == NULL) - return; - shape = image->shapes; - while (shape != NULL) { - snext = shape->next; - nsvg__deletePaths(shape->paths); - nsvg__deletePaint(&shape->fill); - nsvg__deletePaint(&shape->stroke); - free(shape); - shape = snext; - } - free(image); -} - -#endif diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt index 6ea30f48a13..5499fe36898 100644 --- a/source/blender/io/usd/CMakeLists.txt +++ b/source/blender/io/usd/CMakeLists.txt @@ -56,7 +56,9 @@ set(INC_SYS ) set(SRC - intern/usd_capi.cc + intern/usd_capi_export.cc + intern/usd_capi_import.cc + intern/usd_common.cc intern/usd_hierarchy_iterator.cc intern/usd_writer_abstract.cc intern/usd_writer_camera.cc @@ -66,7 +68,21 @@ set(SRC intern/usd_writer_metaball.cc intern/usd_writer_transform.cc + intern/usd_reader_camera.cc + intern/usd_reader_curve.cc + intern/usd_reader_geom.cc + intern/usd_reader_light.cc + intern/usd_reader_material.cc + intern/usd_reader_mesh.cc + intern/usd_reader_nurbs.cc + intern/usd_reader_prim.cc + intern/usd_reader_stage.cc + intern/usd_reader_xform.cc + intern/usd_reader_volume.cc + usd.h + + intern/usd_common.h intern/usd_exporter_context.h intern/usd_hierarchy_iterator.h intern/usd_writer_abstract.h @@ -76,6 +92,18 @@ set(SRC intern/usd_writer_mesh.h intern/usd_writer_metaball.h intern/usd_writer_transform.h + + intern/usd_reader_camera.h + intern/usd_reader_curve.h + intern/usd_reader_geom.h + intern/usd_reader_light.h + intern/usd_reader_material.h + intern/usd_reader_mesh.h + intern/usd_reader_nurbs.h + intern/usd_reader_prim.h + intern/usd_reader_stage.h + intern/usd_reader_xform.h + intern/usd_reader_volume.h ) set(LIB diff --git a/source/blender/io/usd/intern/usd_capi.cc b/source/blender/io/usd/intern/usd_capi_export.cc index dc2b46e5cea..efa31df25c1 100644 --- a/source/blender/io/usd/intern/usd_capi.cc +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -18,6 +18,7 @@ */ #include "usd.h" +#include "usd_common.h" #include "usd_hierarchy_iterator.h" #include <pxr/base/plug/registry.h> @@ -59,21 +60,6 @@ struct ExportJobData { bool export_ok; }; -static void ensure_usd_plugin_path_registered() -{ - static bool plugin_path_registered = false; - if (plugin_path_registered) { - return; - } - plugin_path_registered = true; - - /* Tell USD which directory to search for its JSON files. If 'datafiles/usd' - * does not exist, the USD library will not be able to read or write any files. */ - const std::string blender_usd_datafiles = BKE_appdir_folder_id(BLENDER_DATAFILES, "usd"); - /* The trailing slash indicates to the USD library that the path is a directory. */ - pxr::PlugRegistry::GetInstance().RegisterPlugins(blender_usd_datafiles + "/"); -} - static void export_startjob(void *customdata, /* Cannot be const, this function implements wm_jobs_start_callback. * NOLINTNEXTLINE: readability-non-const-parameter. */ @@ -116,7 +102,7 @@ static void export_startjob(void *customdata, usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z)); usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit, - pxr::VtValue(scene->unit.scale_length)); + static_cast<double>(scene->unit.scale_length)); usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") + BKE_blender_version_string()); diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc new file mode 100644 index 00000000000..789ff20ba82 --- /dev/null +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -0,0 +1,577 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2019 Blender Foundation. + * All rights reserved. + */ + +#include "IO_types.h" +#include "usd.h" +#include "usd_common.h" +#include "usd_hierarchy_iterator.h" +#include "usd_reader_geom.h" +#include "usd_reader_prim.h" +#include "usd_reader_stage.h" + +#include "BKE_appdir.h" +#include "BKE_blender_version.h" +#include "BKE_cachefile.h" +#include "BKE_cdderivedmesh.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_library.h" +#include "BKE_main.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_scene.h" +#include "BKE_world.h" + +#include "BLI_fileops.h" +#include "BLI_listbase.h" +#include "BLI_math_matrix.h" +#include "BLI_math_rotation.h" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" + +#include "DNA_cachefile_types.h" +#include "DNA_collection_types.h" +#include "DNA_node_types.h" +#include "DNA_scene_types.h" +#include "DNA_world_types.h" + +#include "MEM_guardedalloc.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include <pxr/usd/usd/stage.h> +#include <pxr/usd/usdGeom/metrics.h> +#include <pxr/usd/usdGeom/scope.h> +#include <pxr/usd/usdGeom/tokens.h> +#include <pxr/usd/usdGeom/xformCommonAPI.h> + +#include <iostream> + +namespace blender::io::usd { + +static CacheArchiveHandle *handle_from_stage_reader(USDStageReader *reader) +{ + return reinterpret_cast<CacheArchiveHandle *>(reader); +} + +static USDStageReader *stage_reader_from_handle(CacheArchiveHandle *handle) +{ + return reinterpret_cast<USDStageReader *>(handle); +} + +static bool gather_objects_paths(const pxr::UsdPrim &object, ListBase *object_paths) +{ + if (!object.IsValid()) { + return false; + } + + for (const pxr::UsdPrim &childPrim : object.GetChildren()) { + gather_objects_paths(childPrim, object_paths); + } + + void *usd_path_void = MEM_callocN(sizeof(CacheObjectPath), "CacheObjectPath"); + CacheObjectPath *usd_path = static_cast<CacheObjectPath *>(usd_path_void); + + BLI_strncpy(usd_path->path, object.GetPrimPath().GetString().c_str(), sizeof(usd_path->path)); + BLI_addtail(object_paths, usd_path); + + return true; +} + +/* Update the given import settings with the global rotation matrix to orient + * imported objects with Z-up, if necessary */ +static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings *r_settings) +{ + if (!stage || pxr::UsdGeomGetStageUpAxis(stage) == pxr::UsdGeomTokens->z) { + return; + } + + if (!r_settings) { + return; + } + + r_settings->do_convert_mat = true; + + /* Rotate 90 degrees about the X-axis. */ + float rmat[3][3]; + float axis[3] = {1.0f, 0.0f, 0.0f}; + axis_angle_normalized_to_mat3(rmat, axis, M_PI / 2.0f); + + unit_m4(r_settings->conversion_mat); + copy_m4_m3(r_settings->conversion_mat, rmat); +} + +enum { + USD_NO_ERROR = 0, + USD_ARCHIVE_FAIL, +}; + +struct ImportJobData { + Main *bmain; + Scene *scene; + ViewLayer *view_layer; + wmWindowManager *wm; + + char filename[1024]; + USDImportParams params; + ImportSettings settings; + + USDStageReader *archive; + + short *stop; + short *do_update; + float *progress; + + char error_code; + bool was_canceled; + bool import_ok; +}; + +static void import_startjob(void *customdata, short *stop, short *do_update, float *progress) +{ + ImportJobData *data = static_cast<ImportJobData *>(customdata); + + data->stop = stop; + data->do_update = do_update; + data->progress = progress; + data->was_canceled = false; + data->archive = nullptr; + + WM_set_locked_interface(data->wm, true); + G.is_break = false; + + if (data->params.create_collection) { + char display_name[1024]; + BLI_path_to_display_name( + display_name, strlen(data->filename), BLI_path_basename(data->filename)); + Collection *import_collection = BKE_collection_add( + data->bmain, data->scene->master_collection, display_name); + id_fake_user_set(&import_collection->id); + + DEG_id_tag_update(&import_collection->id, ID_RECALC_COPY_ON_WRITE); + DEG_relations_tag_update(data->bmain); + + WM_main_add_notifier(NC_SCENE | ND_LAYER, nullptr); + + data->view_layer->active_collection = BKE_layer_collection_first_from_scene_collection( + data->view_layer, import_collection); + } + + BLI_path_abs(data->filename, BKE_main_blendfile_path_from_global()); + + CacheFile *cache_file = static_cast<CacheFile *>( + BKE_cachefile_add(data->bmain, BLI_path_basename(data->filename))); + + /* Decrement the ID ref-count because it is going to be incremented for each + * modifier and constraint that it will be attached to, so since currently + * it is not used by anyone, its use count will off by one. */ + id_us_min(&cache_file->id); + + cache_file->is_sequence = data->params.is_sequence; + cache_file->scale = data->params.scale; + STRNCPY(cache_file->filepath, data->filename); + + data->settings.cache_file = cache_file; + + *data->do_update = true; + *data->progress = 0.05f; + + if (G.is_break) { + data->was_canceled = true; + return; + } + + *data->do_update = true; + *data->progress = 0.1f; + + pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(data->filename); + + if (!stage) { + WM_reportf(RPT_ERROR, "USD Import: unable to open stage to read %s", data->filename); + data->import_ok = false; + return; + } + + convert_to_z_up(stage, &data->settings); + + /* Set up the stage for animated data. */ + if (data->params.set_frame_range) { + data->scene->r.sfra = stage->GetStartTimeCode(); + data->scene->r.efra = stage->GetEndTimeCode(); + } + + *data->progress = 0.15f; + + USDStageReader *archive = new USDStageReader(stage, data->params, data->settings); + + data->archive = archive; + + archive->collect_readers(data->bmain); + + *data->progress = 0.2f; + + const float size = static_cast<float>(archive->readers().size()); + size_t i = 0; + + /* Setup parenthood */ + + for (USDPrimReader *reader : archive->readers()) { + + if (!reader) { + continue; + } + + Object *ob = reader->object(); + + reader->read_object_data(data->bmain, 0.0); + + USDPrimReader *parent = reader->parent(); + + if (parent == nullptr) { + ob->parent = nullptr; + } + else { + ob->parent = parent->object(); + } + + *data->progress = 0.2f + 0.8f * (++i / size); + *data->do_update = true; + + if (G.is_break) { + data->was_canceled = true; + return; + } + } + + data->import_ok = !data->was_canceled; + + *progress = 1.0f; + *do_update = true; +} + +static void import_endjob(void *customdata) +{ + ImportJobData *data = static_cast<ImportJobData *>(customdata); + + /* Delete objects on cancellation. */ + if (data->was_canceled && data->archive) { + + for (USDPrimReader *reader : data->archive->readers()) { + + if (!reader) { + continue; + } + + /* It's possible that cancellation occurred between the creation of + * the reader and the creation of the Blender object. */ + if (Object *ob = reader->object()) { + BKE_id_free_us(data->bmain, ob); + } + } + } + else if (data->archive) { + /* Add object to scene. */ + Base *base; + LayerCollection *lc; + ViewLayer *view_layer = data->view_layer; + + BKE_view_layer_base_deselect_all(view_layer); + + lc = BKE_layer_collection_get_active(view_layer); + + for (USDPrimReader *reader : data->archive->readers()) { + + if (!reader) { + continue; + } + + Object *ob = reader->object(); + + if (!ob) { + continue; + } + + BKE_collection_object_add(data->bmain, lc->collection, ob); + + base = BKE_view_layer_base_find(view_layer, ob); + /* TODO: is setting active needed? */ + BKE_view_layer_base_select_and_set_active(view_layer, base); + + DEG_id_tag_update(&lc->collection->id, ID_RECALC_COPY_ON_WRITE); + DEG_id_tag_update_ex(data->bmain, + &ob->id, + ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION | + ID_RECALC_BASE_FLAGS); + } + + DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS); + DEG_relations_tag_update(data->bmain); + } + + WM_set_locked_interface(data->wm, false); + + switch (data->error_code) { + default: + case USD_NO_ERROR: + data->import_ok = !data->was_canceled; + break; + case USD_ARCHIVE_FAIL: + WM_report(RPT_ERROR, "Could not open USD archive for reading! See console for detail."); + break; + } + + WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene); +} + +static void import_freejob(void *user_data) +{ + ImportJobData *data = static_cast<ImportJobData *>(user_data); + + delete data->archive; + delete data; +} + +} // namespace blender::io::usd + +using namespace blender::io::usd; + +bool USD_import(struct bContext *C, + const char *filepath, + const USDImportParams *params, + bool as_background_job) +{ + blender::io::usd::ensure_usd_plugin_path_registered(); + + /* Using new here since `MEM_*` functions do not call constructor to properly initialize data. */ + ImportJobData *job = new ImportJobData(); + job->bmain = CTX_data_main(C); + job->scene = CTX_data_scene(C); + job->view_layer = CTX_data_view_layer(C); + job->wm = CTX_wm_manager(C); + job->import_ok = false; + BLI_strncpy(job->filename, filepath, 1024); + + job->settings.scale = params->scale; + job->settings.sequence_offset = params->offset; + job->settings.is_sequence = params->is_sequence; + job->settings.sequence_len = params->sequence_len; + job->settings.validate_meshes = params->validate_meshes; + job->settings.sequence_len = params->sequence_len; + job->error_code = USD_NO_ERROR; + job->was_canceled = false; + job->archive = nullptr; + + job->params = *params; + + G.is_break = false; + + bool import_ok = false; + if (as_background_job) { + wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + job->scene, + "USD Import", + WM_JOB_PROGRESS, + WM_JOB_TYPE_ALEMBIC); + + /* setup job */ + WM_jobs_customdata_set(wm_job, job, import_freejob); + WM_jobs_timer(wm_job, 0.1, NC_SCENE, NC_SCENE); + WM_jobs_callbacks(wm_job, import_startjob, nullptr, nullptr, import_endjob); + + WM_jobs_start(CTX_wm_manager(C), wm_job); + } + else { + /* Fake a job context, so that we don't need NULL pointer checks while importing. */ + short stop = 0, do_update = 0; + float progress = 0.f; + + import_startjob(job, &stop, &do_update, &progress); + import_endjob(job); + import_ok = job->import_ok; + + import_freejob(job); + } + + return import_ok; +} + +/* TODO(makowalski): Extend this function with basic validation that the + * USD reader is compatible with the type of the given (currently unused) 'ob' + * Object parameter, similar to the logic in get_abc_reader() in the + * Alembic importer code. */ +static USDPrimReader *get_usd_reader(CacheReader *reader, Object * /* ob */, const char **err_str) +{ + USDPrimReader *usd_reader = reinterpret_cast<USDPrimReader *>(reader); + pxr::UsdPrim iobject = usd_reader->prim(); + + if (!iobject.IsValid()) { + *err_str = "Invalid object: verify object path"; + return nullptr; + } + + return usd_reader; +} + +struct Mesh *USD_read_mesh(struct CacheReader *reader, + struct Object *ob, + struct Mesh *existing_mesh, + const float time, + const char **err_str, + const int read_flag) +{ + USDGeomReader *usd_reader = dynamic_cast<USDGeomReader *>(get_usd_reader(reader, ob, err_str)); + + if (usd_reader == nullptr) { + return nullptr; + } + + return usd_reader->read_mesh(existing_mesh, time, read_flag, err_str); +} + +bool USD_mesh_topology_changed( + CacheReader *reader, Object *ob, Mesh *existing_mesh, const float time, const char **err_str) +{ + USDGeomReader *usd_reader = dynamic_cast<USDGeomReader *>(get_usd_reader(reader, ob, err_str)); + + if (usd_reader == nullptr) { + return false; + } + + return usd_reader->topology_changed(existing_mesh, time); +} + +void USD_CacheReader_incref(CacheReader *reader) +{ + USDPrimReader *usd_reader = reinterpret_cast<USDPrimReader *>(reader); + usd_reader->incref(); +} + +CacheReader *CacheReader_open_usd_object(CacheArchiveHandle *handle, + CacheReader *reader, + Object *object, + const char *object_path) +{ + if (object_path[0] == '\0') { + return reader; + } + + USDStageReader *archive = stage_reader_from_handle(handle); + + if (!archive || !archive->valid()) { + return reader; + } + + pxr::UsdPrim prim = archive->stage()->GetPrimAtPath(pxr::SdfPath(object_path)); + + if (reader) { + USD_CacheReader_free(reader); + } + + /* TODO(makowalski): The handle does not have the proper import params or settings. */ + USDPrimReader *usd_reader = archive->create_reader(prim); + + if (usd_reader == nullptr) { + /* This object is not supported. */ + return nullptr; + } + usd_reader->object(object); + usd_reader->incref(); + + return reinterpret_cast<CacheReader *>(usd_reader); +} + +void USD_CacheReader_free(CacheReader *reader) +{ + USDPrimReader *usd_reader = reinterpret_cast<USDPrimReader *>(reader); + usd_reader->decref(); + + if (usd_reader->refcount() == 0) { + delete usd_reader; + } +} + +CacheArchiveHandle *USD_create_handle(struct Main * /*bmain*/, + const char *filename, + ListBase *object_paths) +{ + pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(filename); + + if (!stage) { + return nullptr; + } + + USDImportParams params{}; + + blender::io::usd::ImportSettings settings{}; + convert_to_z_up(stage, &settings); + + USDStageReader *stage_reader = new USDStageReader(stage, params, settings); + + if (object_paths) { + gather_objects_paths(stage->GetPseudoRoot(), object_paths); + } + + return handle_from_stage_reader(stage_reader); +} + +void USD_free_handle(CacheArchiveHandle *handle) +{ + USDStageReader *stage_reader = stage_reader_from_handle(handle); + delete stage_reader; +} + +void USD_get_transform(struct CacheReader *reader, + float r_mat_world[4][4], + float time, + float scale) +{ + if (!reader) { + return; + } + USDXformReader *usd_reader = reinterpret_cast<USDXformReader *>(reader); + + bool is_constant = false; + + /* Convert from the local matrix we obtain from USD to world coordinates + * for Blender. This conversion is done here rather than by Blender due to + * work around the non-standard interpretation of CONSTRAINT_SPACE_LOCAL in + * BKE_constraint_mat_convertspace(). */ + Object *object = usd_reader->object(); + if (object->parent == nullptr) { + /* No parent, so local space is the same as world space. */ + usd_reader->read_matrix(r_mat_world, time, scale, &is_constant); + return; + } + + float mat_parent[4][4]; + BKE_object_get_parent_matrix(object, object->parent, mat_parent); + + float mat_local[4][4]; + usd_reader->read_matrix(mat_local, time, scale, &is_constant); + mul_m4_m4m4(r_mat_world, mat_parent, object->parentinv); + mul_m4_m4m4(r_mat_world, r_mat_world, mat_local); +} diff --git a/source/blender/io/usd/intern/usd_common.cc b/source/blender/io/usd/intern/usd_common.cc new file mode 100644 index 00000000000..0cd9c3019ef --- /dev/null +++ b/source/blender/io/usd/intern/usd_common.cc @@ -0,0 +1,43 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ + +#include "usd_common.h" + +#include <pxr/base/plug/registry.h> + +#include "BKE_appdir.h" + +namespace blender::io::usd { + +void ensure_usd_plugin_path_registered() +{ + static bool plugin_path_registered = false; + if (plugin_path_registered) { + return; + } + plugin_path_registered = true; + + /* Tell USD which directory to search for its JSON files. If 'datafiles/usd' + * does not exist, the USD library will not be able to read or write any files. */ + const std::string blender_usd_datafiles = BKE_appdir_folder_id(BLENDER_DATAFILES, "usd"); + /* The trailing slash indicates to the USD library that the path is a directory. */ + pxr::PlugRegistry::GetInstance().RegisterPlugins(blender_usd_datafiles + "/"); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_common.h b/source/blender/io/usd/intern/usd_common.h new file mode 100644 index 00000000000..36667bbc6b1 --- /dev/null +++ b/source/blender/io/usd/intern/usd_common.h @@ -0,0 +1,25 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ +#pragma once + +namespace blender::io::usd { + +void ensure_usd_plugin_path_registered(); + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_camera.cc b/source/blender/io/usd/intern/usd_reader_camera.cc new file mode 100644 index 00000000000..2732ed5770d --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_camera.cc @@ -0,0 +1,100 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_camera.h" + +#include "DNA_camera_types.h" +#include "DNA_object_types.h" + +#include "BKE_camera.h" +#include "BKE_object.h" + +#include "BLI_math.h" + +#include <pxr/pxr.h> +#include <pxr/usd/usdGeom/camera.h> + +namespace blender::io::usd { + +void USDCameraReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + Camera *bcam = static_cast<Camera *>(BKE_camera_add(bmain, name_.c_str())); + + object_ = BKE_object_add_only_object(bmain, OB_CAMERA, name_.c_str()); + object_->data = bcam; +} + +void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTime) +{ + Camera *bcam = (Camera *)object_->data; + + pxr::UsdGeomCamera cam_prim(prim_); + + if (!cam_prim) { + return; + } + + pxr::VtValue val; + cam_prim.GetFocalLengthAttr().Get(&val, motionSampleTime); + pxr::VtValue verApOffset; + cam_prim.GetVerticalApertureOffsetAttr().Get(&verApOffset, motionSampleTime); + pxr::VtValue horApOffset; + cam_prim.GetHorizontalApertureOffsetAttr().Get(&horApOffset, motionSampleTime); + pxr::VtValue clippingRangeVal; + cam_prim.GetClippingRangeAttr().Get(&clippingRangeVal, motionSampleTime); + pxr::VtValue focalDistanceVal; + cam_prim.GetFocusDistanceAttr().Get(&focalDistanceVal, motionSampleTime); + pxr::VtValue fstopVal; + cam_prim.GetFStopAttr().Get(&fstopVal, motionSampleTime); + pxr::VtValue projectionVal; + cam_prim.GetProjectionAttr().Get(&projectionVal, motionSampleTime); + pxr::VtValue verAp; + cam_prim.GetVerticalApertureAttr().Get(&verAp, motionSampleTime); + pxr::VtValue horAp; + cam_prim.GetHorizontalApertureAttr().Get(&horAp, motionSampleTime); + + bcam->lens = val.Get<float>(); + /* TODO(makowalski) */ +#if 0 + bcam->sensor_x = 0.0f; + bcam->sensor_y = 0.0f; +#endif + bcam->shiftx = verApOffset.Get<float>(); + bcam->shifty = horApOffset.Get<float>(); + + bcam->type = (projectionVal.Get<pxr::TfToken>().GetString() == "perspective") ? CAM_PERSP : + CAM_ORTHO; + + /* Calling UncheckedGet() to silence compiler warnings. */ + bcam->clip_start = max_ff(0.1f, clippingRangeVal.UncheckedGet<pxr::GfVec2f>()[0]); + bcam->clip_end = clippingRangeVal.UncheckedGet<pxr::GfVec2f>()[1]; + + bcam->dof.focus_distance = focalDistanceVal.Get<float>(); + bcam->dof.aperture_fstop = static_cast<float>(fstopVal.Get<float>()); + + if (bcam->type == CAM_ORTHO) { + bcam->ortho_scale = max_ff(verAp.Get<float>(), horAp.Get<float>()); + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_camera.h b/source/blender/io/usd/intern/usd_reader_camera.h new file mode 100644 index 00000000000..a4156aa8be2 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_camera.h @@ -0,0 +1,42 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_xform.h" + +namespace blender::io::usd { + +class USDCameraReader : public USDXformReader { + + public: + USDCameraReader(const pxr::UsdPrim &object, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDXformReader(object, import_params, settings) + { + } + + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_curve.cc b/source/blender/io/usd/intern/usd_reader_curve.cc new file mode 100644 index 00000000000..31ecf27cf7e --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_curve.cc @@ -0,0 +1,256 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation, + * Copyright (C) 2016 Kévin Dietrich. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_curve.h" + +#include "BKE_curve.h" +#include "BKE_mesh.h" +#include "BKE_object.h" + +#include "BLI_listbase.h" + +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "MEM_guardedalloc.h" + +#include <pxr/base/vt/array.h> +#include <pxr/base/vt/types.h> +#include <pxr/base/vt/value.h> + +#include <pxr/usd/usdGeom/basisCurves.h> +#include <pxr/usd/usdGeom/curves.h> + +namespace blender::io::usd { + +void USDCurvesReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE); + + curve_->flag |= CU_DEFORM_FILL | CU_3D; + curve_->actvert = CU_ACT_NONE; + curve_->resolu = 2; + + object_ = BKE_object_add_only_object(bmain, OB_CURVE, name_.c_str()); + object_->data = curve_; +} + +void USDCurvesReader::read_object_data(Main *bmain, double motionSampleTime) +{ + Curve *cu = (Curve *)object_->data; + read_curve_sample(cu, motionSampleTime); + + if (curve_prim_.GetPointsAttr().ValueMightBeTimeVarying()) { + add_cache_modifier(); + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +void USDCurvesReader::read_curve_sample(Curve *cu, const double motionSampleTime) +{ + curve_prim_ = pxr::UsdGeomBasisCurves(prim_); + + if (!curve_prim_) { + return; + } + + pxr::UsdAttribute widthsAttr = curve_prim_.GetWidthsAttr(); + pxr::UsdAttribute vertexAttr = curve_prim_.GetCurveVertexCountsAttr(); + pxr::UsdAttribute pointsAttr = curve_prim_.GetPointsAttr(); + + pxr::VtIntArray usdCounts; + + vertexAttr.Get(&usdCounts, motionSampleTime); + int num_subcurves = usdCounts.size(); + + pxr::VtVec3fArray usdPoints; + pointsAttr.Get(&usdPoints, motionSampleTime); + + pxr::VtFloatArray usdWidths; + widthsAttr.Get(&usdWidths, motionSampleTime); + + pxr::UsdAttribute basisAttr = curve_prim_.GetBasisAttr(); + pxr::TfToken basis; + basisAttr.Get(&basis, motionSampleTime); + + pxr::UsdAttribute typeAttr = curve_prim_.GetTypeAttr(); + pxr::TfToken type; + typeAttr.Get(&type, motionSampleTime); + + pxr::UsdAttribute wrapAttr = curve_prim_.GetWrapAttr(); + pxr::TfToken wrap; + wrapAttr.Get(&wrap, motionSampleTime); + + pxr::VtVec3fArray usdNormals; + curve_prim_.GetNormalsAttr().Get(&usdNormals, motionSampleTime); + + /* If normals, extrude, else bevel. + * Perhaps to be replaced by Blender/USD Schema. */ + if (!usdNormals.empty()) { + /* Set extrusion to 1.0f. */ + curve_->ext1 = 1.0f; + } + else { + /* Set bevel depth to 1.0f. */ + curve_->ext2 = 1.0f; + } + + size_t idx = 0; + for (size_t i = 0; i < num_subcurves; i++) { + const int num_verts = usdCounts[i]; + Nurb *nu = static_cast<Nurb *>(MEM_callocN(sizeof(Nurb), __func__)); + + if (basis == pxr::UsdGeomTokens->bspline) { + nu->flag = CU_SMOOTH; + nu->type = CU_NURBS; + } + else if (basis == pxr::UsdGeomTokens->bezier) { + /* TODO(makowalski): Beziers are not properly imported as beziers. */ + nu->type = CU_POLY; + } + else if (basis.IsEmpty()) { + nu->type = CU_POLY; + } + nu->resolu = cu->resolu; + nu->resolv = cu->resolv; + + nu->pntsu = num_verts; + nu->pntsv = 1; + + if (type == pxr::UsdGeomTokens->cubic) { + nu->orderu = 4; + } + else if (type == pxr::UsdGeomTokens->linear) { + nu->orderu = 2; + } + + if (wrap == pxr::UsdGeomTokens->periodic) { + nu->flagu |= CU_NURB_CYCLIC; + } + else if (wrap == pxr::UsdGeomTokens->pinned) { + nu->flagu |= CU_NURB_ENDPOINT; + } + + float weight = 1.0f; + + nu->bp = static_cast<BPoint *>(MEM_callocN(sizeof(BPoint) * nu->pntsu, __func__)); + BPoint *bp = nu->bp; + + for (int j = 0; j < nu->pntsu; j++, bp++, idx++) { + bp->vec[0] = (float)usdPoints[idx][0]; + bp->vec[1] = (float)usdPoints[idx][1]; + bp->vec[2] = (float)usdPoints[idx][2]; + bp->vec[3] = weight; + bp->f1 = SELECT; + bp->weight = weight; + + float radius = curve_->width; + if (idx < usdWidths.size()) { + radius = usdWidths[idx]; + } + + bp->radius = radius; + } + + BKE_nurb_knot_calc_u(nu); + BKE_nurb_knot_calc_v(nu); + + BLI_addtail(BKE_curve_nurbs_get(cu), nu); + } +} + +Mesh *USDCurvesReader::read_mesh(struct Mesh *existing_mesh, + const double motionSampleTime, + const int /* read_flag */, + const char ** /* err_str */) +{ + if (!curve_prim_) { + return existing_mesh; + } + + pxr::UsdAttribute widthsAttr = curve_prim_.GetWidthsAttr(); + pxr::UsdAttribute vertexAttr = curve_prim_.GetCurveVertexCountsAttr(); + pxr::UsdAttribute pointsAttr = curve_prim_.GetPointsAttr(); + + pxr::VtIntArray usdCounts; + + vertexAttr.Get(&usdCounts, motionSampleTime); + int num_subcurves = usdCounts.size(); + + pxr::VtVec3fArray usdPoints; + pointsAttr.Get(&usdPoints, motionSampleTime); + + int vertex_idx = 0; + int curve_idx; + Curve *curve = static_cast<Curve *>(object_->data); + + const int curve_count = BLI_listbase_count(&curve->nurb); + bool same_topology = curve_count == num_subcurves; + + if (same_topology) { + Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); + for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { + const int num_in_usd = usdCounts[curve_idx]; + const int num_in_blender = nurbs->pntsu; + + if (num_in_usd != num_in_blender) { + same_topology = false; + break; + } + } + } + + if (!same_topology) { + BKE_nurbList_free(&curve->nurb); + read_curve_sample(curve, motionSampleTime); + } + else { + Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); + for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { + const int totpoint = usdCounts[curve_idx]; + + if (nurbs->bp) { + BPoint *point = nurbs->bp; + + for (int i = 0; i < totpoint; i++, point++, vertex_idx++) { + point->vec[0] = usdPoints[vertex_idx][0]; + point->vec[1] = usdPoints[vertex_idx][1]; + point->vec[2] = usdPoints[vertex_idx][2]; + } + } + else if (nurbs->bezt) { + BezTriple *bezier = nurbs->bezt; + + for (int i = 0; i < totpoint; i++, bezier++, vertex_idx++) { + bezier->vec[1][0] = usdPoints[vertex_idx][0]; + bezier->vec[1][1] = usdPoints[vertex_idx][1]; + bezier->vec[1][2] = usdPoints[vertex_idx][2]; + } + } + } + } + + return BKE_mesh_new_nomain_from_curve(object_); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_curve.h b/source/blender/io/usd/intern/usd_reader_curve.h new file mode 100644 index 00000000000..1e676bbbd02 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_curve.h @@ -0,0 +1,62 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation, + * Copyright (C) 2016 Kévin Dietrich. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_geom.h" + +#include "pxr/usd/usdGeom/basisCurves.h" + +struct Curve; + +namespace blender::io::usd { + +class USDCurvesReader : public USDGeomReader { + protected: + pxr::UsdGeomBasisCurves curve_prim_; + Curve *curve_; + + public: + USDCurvesReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDGeomReader(prim, import_params, settings), curve_prim_(prim), curve_(nullptr) + { + } + + bool valid() const override + { + return static_cast<bool>(curve_prim_); + } + + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; + + void read_curve_sample(Curve *cu, double motionSampleTime); + + Mesh *read_mesh(struct Mesh *existing_mesh, + double motionSampleTime, + int read_flag, + const char **err_str) override; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_geom.cc b/source/blender/io/usd/intern/usd_reader_geom.cc new file mode 100644 index 00000000000..23c5f57120c --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_geom.cc @@ -0,0 +1,59 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_geom.h" + +#include "BKE_lib_id.h" +#include "BKE_modifier.h" +#include "BKE_object.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_math_geom.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "DNA_cachefile_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_space_types.h" /* for FILE_MAX */ + +namespace blender::io::usd { + +void USDGeomReader::add_cache_modifier() +{ + ModifierData *md = BKE_modifier_new(eModifierType_MeshSequenceCache); + BLI_addtail(&object_->modifiers, md); + + MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md); + + mcmd->cache_file = settings_->cache_file; + id_us_plus(&mcmd->cache_file->id); + mcmd->read_flag = import_params_.mesh_read_flag; + + BLI_strncpy(mcmd->object_path, prim_.GetPath().GetString().c_str(), FILE_MAX); +} + +void USDGeomReader::add_subdiv_modifier() +{ + ModifierData *md = BKE_modifier_new(eModifierType_Subsurf); + BLI_addtail(&object_->modifiers, md); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_geom.h b/source/blender/io/usd/intern/usd_reader_geom.h new file mode 100644 index 00000000000..99e22248f2b --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_geom.h @@ -0,0 +1,52 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_xform.h" + +struct Mesh; + +namespace blender::io::usd { + +class USDGeomReader : public USDXformReader { + + public: + USDGeomReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDXformReader(prim, import_params, settings) + { + } + + virtual Mesh *read_mesh(struct Mesh *existing_mesh, + double motionSampleTime, + int read_flag, + const char **err_str) = 0; + + virtual bool topology_changed(Mesh * /* existing_mesh */, double /* motionSampleTime */) + { + return true; + } + + void add_cache_modifier(); + void add_subdiv_modifier(); +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_instance.cc b/source/blender/io/usd/intern/usd_reader_instance.cc new file mode 100644 index 00000000000..e645b0237b9 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_instance.cc @@ -0,0 +1,64 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ + +#include "usd_reader_instance.h" + +#include "BKE_object.h" +#include "DNA_object_types.h" + +#include <iostream> + +namespace blender::io::usd { + +USDInstanceReader::USDInstanceReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDXformReader(prim, import_params, settings) +{ +} + +bool USDInstanceReader::valid() const +{ + return prim_.IsValid() && prim_.IsInstance(); +} + +void USDInstanceReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + this->object_ = BKE_object_add_only_object(bmain, OB_EMPTY, name_.c_str()); + this->object_->data = nullptr; + this->object_->transflag |= OB_DUPLICOLLECTION; +} + +void USDInstanceReader::set_instance_collection(Collection *coll) +{ + if (this->object_) { + this->object_->instance_collection = coll; + } +} + +pxr::SdfPath USDInstanceReader::proto_path() const +{ + if (pxr::UsdPrim master = prim_.GetMaster()) { + return master.GetPath(); + } + + return pxr::SdfPath(); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_instance.h b/source/blender/io/usd/intern/usd_reader_instance.h new file mode 100644 index 00000000000..efc1c69a7dd --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_instance.h @@ -0,0 +1,47 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ +#pragma once + +#include "usd_reader_xform.h" + +#include <pxr/usd/usdGeom/xform.h> + +struct Collection; + +namespace blender::io::usd { + +/* Wraps the UsdGeomXform schema. Creates a Blender Empty object. */ + +class USDInstanceReader : public USDXformReader { + + public: + USDInstanceReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings); + + bool valid() const override; + + void create_object(Main *bmain, double motionSampleTime) override; + + void set_instance_collection(Collection *coll); + + pxr::SdfPath proto_path() const; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_light.cc b/source/blender/io/usd/intern/usd_reader_light.cc new file mode 100644 index 00000000000..fda0c17968a --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_light.cc @@ -0,0 +1,252 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_light.h" + +#include "BKE_light.h" +#include "BKE_object.h" + +#include "DNA_light_types.h" +#include "DNA_object_types.h" + +#include <pxr/usd/usdLux/light.h> + +#include <pxr/usd/usdLux/diskLight.h> +#include <pxr/usd/usdLux/distantLight.h> +#include <pxr/usd/usdLux/rectLight.h> +#include <pxr/usd/usdLux/shapingAPI.h> +#include <pxr/usd/usdLux/sphereLight.h> + +#include <iostream> + +namespace blender::io::usd { + +void USDLightReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + Light *blight = static_cast<Light *>(BKE_light_add(bmain, name_.c_str())); + + object_ = BKE_object_add_only_object(bmain, OB_LAMP, name_.c_str()); + object_->data = blight; +} + +void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime) +{ + Light *blight = (Light *)object_->data; + + if (blight == nullptr) { + return; + } + + if (!prim_) { + return; + } + + pxr::UsdLuxLight light_prim(prim_); + + if (!light_prim) { + return; + } + + pxr::UsdLuxShapingAPI shaping_api(light_prim); + + /* Set light type. */ + + if (prim_.IsA<pxr::UsdLuxDiskLight>()) { + blight->type = LA_AREA; + blight->area_shape = LA_AREA_DISK; + /* Ellipse lights are not currently supported */ + } + else if (prim_.IsA<pxr::UsdLuxRectLight>()) { + blight->type = LA_AREA; + blight->area_shape = LA_AREA_RECT; + } + else if (prim_.IsA<pxr::UsdLuxSphereLight>()) { + blight->type = LA_LOCAL; + + if (shaping_api && shaping_api.GetShapingConeAngleAttr().IsAuthored()) { + blight->type = LA_SPOT; + } + } + else if (prim_.IsA<pxr::UsdLuxDistantLight>()) { + blight->type = LA_SUN; + } + + /* Set light values. */ + + if (pxr::UsdAttribute intensity_attr = light_prim.GetIntensityAttr()) { + float intensity = 0.0f; + if (intensity_attr.Get(&intensity, motionSampleTime)) { + blight->energy = intensity * this->import_params_.light_intensity_scale; + } + } + + /* TODO(makowalsk): Not currently supported. */ +#if 0 + pxr::VtValue exposure; + light_prim.GetExposureAttr().Get(&exposure, motionSampleTime); +#endif + + /* TODO(makowalsk): Not currently supported */ +#if 0 + pxr::VtValue diffuse; + light_prim.GetDiffuseAttr().Get(&diffuse, motionSampleTime); +#endif + + if (pxr::UsdAttribute spec_attr = light_prim.GetSpecularAttr()) { + float spec = 0.0f; + if (spec_attr.Get(&spec, motionSampleTime)) { + blight->spec_fac = spec; + } + } + + if (pxr::UsdAttribute color_attr = light_prim.GetColorAttr()) { + pxr::GfVec3f color; + if (color_attr.Get(&color, motionSampleTime)) { + blight->r = color[0]; + blight->g = color[1]; + blight->b = color[2]; + } + } + + /* TODO(makowalski): Not currently supported. */ +#if 0 + pxr::VtValue use_color_temp; + light_prim.GetEnableColorTemperatureAttr().Get(&use_color_temp, motionSampleTime); +#endif + + /* TODO(makowalski): Not currently supported. */ +#if 0 + pxr::VtValue color_temp; + light_prim.GetColorTemperatureAttr().Get(&color_temp, motionSampleTime); +#endif + + switch (blight->type) { + case LA_AREA: + if (blight->area_shape == LA_AREA_RECT && prim_.IsA<pxr::UsdLuxRectLight>()) { + + pxr::UsdLuxRectLight rect_light(prim_); + + if (!rect_light) { + break; + } + + if (pxr::UsdAttribute width_attr = rect_light.GetWidthAttr()) { + float width = 0.0f; + if (width_attr.Get(&width, motionSampleTime)) { + blight->area_size = width; + } + } + + if (pxr::UsdAttribute height_attr = rect_light.GetHeightAttr()) { + float height = 0.0f; + if (height_attr.Get(&height, motionSampleTime)) { + blight->area_sizey = height; + } + } + } + else if (blight->area_shape == LA_AREA_DISK && prim_.IsA<pxr::UsdLuxDiskLight>()) { + + pxr::UsdLuxDiskLight disk_light(prim_); + + if (!disk_light) { + break; + } + + if (pxr::UsdAttribute radius_attr = disk_light.GetRadiusAttr()) { + float radius = 0.0f; + if (radius_attr.Get(&radius, motionSampleTime)) { + blight->area_size = radius * 2.0f; + } + } + } + break; + case LA_LOCAL: + if (prim_.IsA<pxr::UsdLuxSphereLight>()) { + + pxr::UsdLuxSphereLight sphere_light(prim_); + + if (!sphere_light) { + break; + } + + if (pxr::UsdAttribute radius_attr = sphere_light.GetRadiusAttr()) { + float radius = 0.0f; + if (radius_attr.Get(&radius, motionSampleTime)) { + blight->area_size = radius; + } + } + } + break; + case LA_SPOT: + if (prim_.IsA<pxr::UsdLuxSphereLight>()) { + + pxr::UsdLuxSphereLight sphere_light(prim_); + + if (!sphere_light) { + break; + } + + if (pxr::UsdAttribute radius_attr = sphere_light.GetRadiusAttr()) { + float radius = 0.0f; + if (radius_attr.Get(&radius, motionSampleTime)) { + blight->area_size = radius; + } + } + + if (!shaping_api) { + break; + } + + if (pxr::UsdAttribute cone_angle_attr = shaping_api.GetShapingConeAngleAttr()) { + float cone_angle = 0.0f; + if (cone_angle_attr.Get(&cone_angle, motionSampleTime)) { + blight->spotsize = cone_angle * ((float)M_PI / 180.0f) * 2.0f; + } + } + + if (pxr::UsdAttribute cone_softness_attr = shaping_api.GetShapingConeSoftnessAttr()) { + float cone_softness = 0.0f; + if (cone_softness_attr.Get(&cone_softness, motionSampleTime)) { + blight->spotblend = cone_softness; + } + } + } + break; + case LA_SUN: + if (prim_.IsA<pxr::UsdLuxDistantLight>()) { + pxr::UsdLuxDistantLight distant_light(prim_); + + if (!distant_light) { + break; + } + + if (pxr::UsdAttribute angle_attr = distant_light.GetAngleAttr()) { + float angle = 0.0f; + if (angle_attr.Get(&angle, motionSampleTime)) { + blight->sun_angle = angle * (float)M_PI / 180.0f; + } + } + } + break; + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_light.h b/source/blender/io/usd/intern/usd_reader_light.h new file mode 100644 index 00000000000..e7860fd2c80 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_light.h @@ -0,0 +1,41 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_xform.h" + +namespace blender::io::usd { + +class USDLightReader : public USDXformReader { + + public: + USDLightReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDXformReader(prim, import_params, settings) + { + } + + void create_object(Main *bmain, double motionSampleTime) override; + + void read_object_data(Main *bmain, double motionSampleTime) override; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_material.cc b/source/blender/io/usd/intern/usd_reader_material.cc new file mode 100644 index 00000000000..02ed7c35e57 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_material.cc @@ -0,0 +1,703 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 NVIDIA Corporation. + * All rights reserved. + */ + +#include "usd_reader_material.h" + +#include "BKE_image.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_node.h" + +#include "BLI_math_vector.h" +#include "BLI_string.h" + +#include "DNA_material_types.h" + +#include <pxr/base/gf/vec3f.h> +#include <pxr/usd/usdShade/material.h> +#include <pxr/usd/usdShade/shader.h> + +#include <iostream> +#include <vector> + +namespace usdtokens { + +/* Parameter names. */ +static const pxr::TfToken a("a", pxr::TfToken::Immortal); +static const pxr::TfToken b("b", pxr::TfToken::Immortal); +static const pxr::TfToken clearcoat("clearcoat", pxr::TfToken::Immortal); +static const pxr::TfToken clearcoatRoughness("clearcoatRoughness", pxr::TfToken::Immortal); +static const pxr::TfToken diffuseColor("diffuseColor", pxr::TfToken::Immortal); +static const pxr::TfToken emissiveColor("emissiveColor", pxr::TfToken::Immortal); +static const pxr::TfToken file("file", pxr::TfToken::Immortal); +static const pxr::TfToken g("g", pxr::TfToken::Immortal); +static const pxr::TfToken ior("ior", pxr::TfToken::Immortal); +static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal); +static const pxr::TfToken normal("normal", pxr::TfToken::Immortal); +static const pxr::TfToken occlusion("occlusion", pxr::TfToken::Immortal); +static const pxr::TfToken opacity("opacity", pxr::TfToken::Immortal); +static const pxr::TfToken opacityThreshold("opacityThreshold", pxr::TfToken::Immortal); +static const pxr::TfToken r("r", pxr::TfToken::Immortal); +static const pxr::TfToken result("result", pxr::TfToken::Immortal); +static const pxr::TfToken rgb("rgb", pxr::TfToken::Immortal); +static const pxr::TfToken rgba("rgba", pxr::TfToken::Immortal); +static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal); +static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal); +static const pxr::TfToken specularColor("specularColor", pxr::TfToken::Immortal); +static const pxr::TfToken st("st", pxr::TfToken::Immortal); +static const pxr::TfToken varname("varname", pxr::TfToken::Immortal); + +/* Color space names. */ +static const pxr::TfToken raw("raw", pxr::TfToken::Immortal); +static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal); + +/* USD shader names. */ +static const pxr::TfToken UsdPreviewSurface("UsdPreviewSurface", pxr::TfToken::Immortal); +static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2", + pxr::TfToken::Immortal); +static const pxr::TfToken UsdUVTexture("UsdUVTexture", pxr::TfToken::Immortal); +} // namespace usdtokens + +/* Add a node of the given type at the given location coordinates. */ +static bNode *add_node( + const bContext *C, bNodeTree *ntree, const int type, const float locx, const float locy) +{ + bNode *new_node = nodeAddStaticNode(C, ntree, type); + + if (new_node) { + new_node->locx = locx; + new_node->locy = locy; + } + + return new_node; +} + +/* Connect the output socket of node 'source' to the input socket of node 'dest'. */ +static void link_nodes( + bNodeTree *ntree, bNode *source, const char *sock_out, bNode *dest, const char *sock_in) +{ + bNodeSocket *source_socket = nodeFindSocket(source, SOCK_OUT, sock_out); + + if (!source_socket) { + std::cerr << "PROGRAMMER ERROR: Couldn't find output socket " << sock_out << std::endl; + return; + } + + bNodeSocket *dest_socket = nodeFindSocket(dest, SOCK_IN, sock_in); + + if (!dest_socket) { + std::cerr << "PROGRAMMER ERROR: Couldn't find input socket " << sock_in << std::endl; + return; + } + + nodeAddLink(ntree, source, source_socket, dest, dest_socket); +} + +/* Returns true if the given shader may have opacity < 1.0, based + * on heuristics. */ +static bool needs_blend(const pxr::UsdShadeShader &usd_shader) +{ + if (!usd_shader) { + return false; + } + + bool needs_blend = false; + + if (pxr::UsdShadeInput opacity_input = usd_shader.GetInput(usdtokens::opacity)) { + + if (opacity_input.HasConnectedSource()) { + needs_blend = true; + } + else { + pxr::VtValue val; + if (opacity_input.GetAttr().HasAuthoredValue() && opacity_input.GetAttr().Get(&val)) { + float opacity = val.Get<float>(); + needs_blend = opacity < 1.0f; + } + } + } + + return needs_blend; +} + +/* Returns the given shader's opacityThreshold input value, if this input has an + * authored value. Otherwise, returns the given default value. */ +static float get_opacity_threshold(const pxr::UsdShadeShader &usd_shader, + float default_value = 0.0f) +{ + if (!usd_shader) { + return default_value; + } + + pxr::UsdShadeInput opacity_threshold_input = usd_shader.GetInput(usdtokens::opacityThreshold); + + if (!opacity_threshold_input) { + return default_value; + } + + pxr::VtValue val; + if (opacity_threshold_input.GetAttr().HasAuthoredValue() && + opacity_threshold_input.GetAttr().Get(&val)) { + return val.Get<float>(); + } + + return default_value; +} + +static pxr::TfToken get_source_color_space(const pxr::UsdShadeShader &usd_shader) +{ + if (!usd_shader) { + return pxr::TfToken(); + } + + pxr::UsdShadeInput color_space_input = usd_shader.GetInput(usdtokens::sourceColorSpace); + + if (!color_space_input) { + return pxr::TfToken(); + } + + pxr::VtValue color_space_val; + if (color_space_input.Get(&color_space_val) && color_space_val.IsHolding<pxr::TfToken>()) { + return color_space_val.Get<pxr::TfToken>(); + } + + return pxr::TfToken(); +} + +/* Attempts to return in r_preview_surface the UsdPreviewSurface shader source + * of the given material. Returns true if a UsdPreviewSurface source was found + * and returns false otherwise. */ +static bool get_usd_preview_surface(const pxr::UsdShadeMaterial &usd_material, + pxr::UsdShadeShader &r_preview_surface) +{ + if (!usd_material) { + return false; + } + + if (pxr::UsdShadeShader surf_shader = usd_material.ComputeSurfaceSource()) { + /* Check if we have a UsdPreviewSurface shader. */ + pxr::TfToken shader_id; + if (surf_shader.GetShaderId(&shader_id) && shader_id == usdtokens::UsdPreviewSurface) { + r_preview_surface = surf_shader; + return true; + } + } + + return false; +} + +/* Set the Blender material's viewport display color, metallic and roughness + * properties from the given USD preview surface shader's inputs. */ +static void set_viewport_material_props(Material *mtl, const pxr::UsdShadeShader &usd_preview) +{ + if (!(mtl && usd_preview)) { + return; + } + + if (pxr::UsdShadeInput diffuse_color_input = usd_preview.GetInput(usdtokens::diffuseColor)) { + pxr::VtValue val; + if (diffuse_color_input.GetAttr().HasAuthoredValue() && + diffuse_color_input.GetAttr().Get(&val) && val.IsHolding<pxr::GfVec3f>()) { + pxr::GfVec3f color = val.UncheckedGet<pxr::GfVec3f>(); + mtl->r = color[0]; + mtl->g = color[1]; + mtl->b = color[2]; + } + } + + if (pxr::UsdShadeInput metallic_input = usd_preview.GetInput(usdtokens::metallic)) { + pxr::VtValue val; + if (metallic_input.GetAttr().HasAuthoredValue() && metallic_input.GetAttr().Get(&val) && + val.IsHolding<float>()) { + mtl->metallic = val.Get<float>(); + } + } + + if (pxr::UsdShadeInput roughness_input = usd_preview.GetInput(usdtokens::roughness)) { + pxr::VtValue val; + if (roughness_input.GetAttr().HasAuthoredValue() && roughness_input.GetAttr().Get(&val) && + val.IsHolding<float>()) { + mtl->roughness = val.Get<float>(); + } + } +} + +namespace blender::io::usd { + +namespace { + +/* Compute the x- and y-coordinates for placing a new node in an unoccupied region of + * the column with the given index. Returns the coordinates in r_locx and r_locy and + * updates the column-occupancy information in r_ctx. */ +void compute_node_loc(const int column, float *r_locx, float *r_locy, NodePlacementContext *r_ctx) +{ + if (!(r_locx && r_locy && r_ctx)) { + return; + } + + (*r_locx) = r_ctx->origx - column * r_ctx->horizontal_step; + + if (column >= r_ctx->column_offsets.size()) { + r_ctx->column_offsets.push_back(0.0f); + } + + (*r_locy) = r_ctx->origy - r_ctx->column_offsets[column]; + + /* Record the y-offset of the occupied region in + * the column, including padding. */ + r_ctx->column_offsets[column] += r_ctx->vertical_step + 10.0f; +} + +} // End anonymous namespace. + +USDMaterialReader::USDMaterialReader(const USDImportParams ¶ms, Main *bmain) + : params_(params), bmain_(bmain) +{ +} + +Material *USDMaterialReader::add_material(const pxr::UsdShadeMaterial &usd_material) const +{ + if (!(bmain_ && usd_material)) { + return nullptr; + } + + std::string mtl_name = usd_material.GetPrim().GetName().GetString(); + + /* Create the material. */ + Material *mtl = BKE_material_add(bmain_, mtl_name.c_str()); + + /* Get the UsdPreviewSurface shader source for the material, + * if there is one. */ + pxr::UsdShadeShader usd_preview; + if (get_usd_preview_surface(usd_material, usd_preview)) { + + set_viewport_material_props(mtl, usd_preview); + + /* Optionally, create shader nodes to represent a UsdPreviewSurface. */ + if (params_.import_usd_preview) { + import_usd_preview(mtl, usd_preview); + } + } + + return mtl; +} + +/* Create the Principled BSDF shader node network. */ +void USDMaterialReader::import_usd_preview(Material *mtl, + const pxr::UsdShadeShader &usd_shader) const +{ + if (!(bmain_ && mtl && usd_shader)) { + return; + } + + /* Create the Material's node tree containing the principled BSDF + * and output shaders. */ + + /* Add the node tree. */ + bNodeTree *ntree = ntreeAddTree(nullptr, "Shader Nodetree", "ShaderNodeTree"); + mtl->nodetree = ntree; + mtl->use_nodes = true; + + /* Create the Principled BSDF shader node. */ + bNode *principled = add_node(nullptr, ntree, SH_NODE_BSDF_PRINCIPLED, 0.0f, 300.0f); + + if (!principled) { + std::cerr << "ERROR: Couldn't create SH_NODE_BSDF_PRINCIPLED node for USD shader " + << usd_shader.GetPath() << std::endl; + return; + } + + /* Create the material output node. */ + bNode *output = add_node(nullptr, ntree, SH_NODE_OUTPUT_MATERIAL, 300.0f, 300.0f); + + if (!output) { + std::cerr << "ERROR: Couldn't create SH_NODE_OUTPUT_MATERIAL node for USD shader " + << usd_shader.GetPath() << std::endl; + return; + } + + /* Connect the Principled BSDF node to the output node. */ + link_nodes(ntree, principled, "BSDF", output, "Surface"); + + /* Recursively create the principled shader input networks. */ + set_principled_node_inputs(principled, ntree, usd_shader); + + nodeSetActive(ntree, output); + + ntreeUpdateTree(bmain_, ntree); + + /* Optionally, set the material blend mode. */ + + if (params_.set_material_blend) { + if (needs_blend(usd_shader)) { + float opacity_threshold = get_opacity_threshold(usd_shader, 0.0f); + if (opacity_threshold > 0.0f) { + mtl->blend_method = MA_BM_CLIP; + mtl->alpha_threshold = opacity_threshold; + } + else { + mtl->blend_method = MA_BM_BLEND; + } + } + } +} + +void USDMaterialReader::set_principled_node_inputs(bNode *principled, + bNodeTree *ntree, + const pxr::UsdShadeShader &usd_shader) const +{ + /* The context struct keeps track of the locations for adding + * input nodes. */ + NodePlacementContext context(0.0f, 300.0); + + /* The column index (from right to left relative to the principled + * node) where we're adding the nodes. */ + int column = 0; + + /* Recursively set the principled shader inputs. */ + + if (pxr::UsdShadeInput diffuse_input = usd_shader.GetInput(usdtokens::diffuseColor)) { + set_node_input(diffuse_input, principled, "Base Color", ntree, column, &context); + } + + if (pxr::UsdShadeInput emissive_input = usd_shader.GetInput(usdtokens::emissiveColor)) { + set_node_input(emissive_input, principled, "Emission", ntree, column, &context); + } + + if (pxr::UsdShadeInput specular_input = usd_shader.GetInput(usdtokens::specularColor)) { + set_node_input(specular_input, principled, "Specular", ntree, column, &context); + } + + if (pxr::UsdShadeInput metallic_input = usd_shader.GetInput(usdtokens::metallic)) { + ; + set_node_input(metallic_input, principled, "Metallic", ntree, column, &context); + } + + if (pxr::UsdShadeInput roughness_input = usd_shader.GetInput(usdtokens::roughness)) { + set_node_input(roughness_input, principled, "Roughness", ntree, column, &context); + } + + if (pxr::UsdShadeInput clearcoat_input = usd_shader.GetInput(usdtokens::clearcoat)) { + set_node_input(clearcoat_input, principled, "Clearcoat", ntree, column, &context); + } + + if (pxr::UsdShadeInput clearcoat_roughness_input = usd_shader.GetInput( + usdtokens::clearcoatRoughness)) { + set_node_input( + clearcoat_roughness_input, principled, "Clearcoat Roughness", ntree, column, &context); + } + + if (pxr::UsdShadeInput opacity_input = usd_shader.GetInput(usdtokens::opacity)) { + set_node_input(opacity_input, principled, "Alpha", ntree, column, &context); + } + + if (pxr::UsdShadeInput ior_input = usd_shader.GetInput(usdtokens::ior)) { + set_node_input(ior_input, principled, "IOR", ntree, column, &context); + } + + if (pxr::UsdShadeInput normal_input = usd_shader.GetInput(usdtokens::normal)) { + set_node_input(normal_input, principled, "Normal", ntree, column, &context); + } +} + +/* Convert the given USD shader input to an input on the given Blender node. */ +void USDMaterialReader::set_node_input(const pxr::UsdShadeInput &usd_input, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + const int column, + NodePlacementContext *r_ctx) const +{ + if (!(usd_input && dest_node && r_ctx)) { + return; + } + + if (usd_input.HasConnectedSource()) { + /* The USD shader input has a connected source shader. Follow the connection + * and attempt to convert the connected USD shader to a Blender node. */ + follow_connection(usd_input, dest_node, dest_socket_name, ntree, column, r_ctx); + } + else { + /* Set the destination node socket value from the USD shader input value. */ + + bNodeSocket *sock = nodeFindSocket(dest_node, SOCK_IN, dest_socket_name); + if (!sock) { + std::cerr << "ERROR: couldn't get destination node socket " << dest_socket_name << std::endl; + return; + } + + pxr::VtValue val; + if (!usd_input.Get(&val)) { + std::cerr << "ERROR: couldn't get value for usd shader input " + << usd_input.GetPrim().GetPath() << std::endl; + return; + } + + switch (sock->type) { + case SOCK_FLOAT: + if (val.IsHolding<float>()) { + ((bNodeSocketValueFloat *)sock->default_value)->value = val.UncheckedGet<float>(); + } + else if (val.IsHolding<pxr::GfVec3f>()) { + pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>(); + float average = (v3f[0] + v3f[1] + v3f[2]) / 3.0f; + ((bNodeSocketValueFloat *)sock->default_value)->value = average; + } + break; + case SOCK_RGBA: + if (val.IsHolding<pxr::GfVec3f>()) { + pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>(); + copy_v3_v3(((bNodeSocketValueRGBA *)sock->default_value)->value, v3f.data()); + } + break; + case SOCK_VECTOR: + if (val.IsHolding<pxr::GfVec3f>()) { + pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>(); + copy_v3_v3(((bNodeSocketValueVector *)sock->default_value)->value, v3f.data()); + } + else if (val.IsHolding<pxr::GfVec2f>()) { + pxr::GfVec2f v2f = val.UncheckedGet<pxr::GfVec2f>(); + copy_v2_v2(((bNodeSocketValueVector *)sock->default_value)->value, v2f.data()); + } + break; + default: + std::cerr << "WARNING: unexpected type " << sock->idname << " for destination node socket " + << dest_socket_name << std::endl; + break; + } + } +} + +/* Follow the connected source of the USD input to create corresponding inputs + * for the given Blender node. */ +void USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + int column, + NodePlacementContext *r_ctx) const +{ + if (!(usd_input && dest_node && dest_socket_name && ntree && r_ctx)) { + return; + } + + pxr::UsdShadeConnectableAPI source; + pxr::TfToken source_name; + pxr::UsdShadeAttributeType source_type; + + usd_input.GetConnectedSource(&source, &source_name, &source_type); + + if (!(source && source.GetPrim().IsA<pxr::UsdShadeShader>())) { + return; + } + + pxr::UsdShadeShader source_shader(source.GetPrim()); + + if (!source_shader) { + return; + } + + pxr::TfToken shader_id; + if (!source_shader.GetShaderId(&shader_id)) { + std::cerr << "ERROR: couldn't get shader id for source shader " + << source_shader.GetPrim().GetPath() << std::endl; + return; + } + + /* For now, only convert UsdUVTexture and UsdPrimvarReader_float2 inputs. */ + if (shader_id == usdtokens::UsdUVTexture) { + + if (strcmp(dest_socket_name, "Normal") == 0) { + + /* The normal texture input requires creating a normal map node. */ + float locx = 0.0f; + float locy = 0.0f; + compute_node_loc(column + 1, &locx, &locy, r_ctx); + + bNode *normal_map = add_node(nullptr, ntree, SH_NODE_NORMAL_MAP, locx, locy); + + /* Currently, the Normal Map node has Tangent Space as the default, + * which is what we need, so we don't need to explicitly set it. */ + + /* Connect the Normal Map to the Normal input. */ + link_nodes(ntree, normal_map, "Normal", dest_node, "Normal"); + + /* Now, create the Texture Image node input to the Normal Map "Color" input. */ + convert_usd_uv_texture( + source_shader, source_name, normal_map, "Color", ntree, column + 2, r_ctx); + } + else { + convert_usd_uv_texture( + source_shader, source_name, dest_node, dest_socket_name, ntree, column + 1, r_ctx); + } + } + else if (shader_id == usdtokens::UsdPrimvarReader_float2) { + convert_usd_primvar_reader_float2( + source_shader, source_name, dest_node, dest_socket_name, ntree, column + 1, r_ctx); + } +} + +void USDMaterialReader::convert_usd_uv_texture(const pxr::UsdShadeShader &usd_shader, + const pxr::TfToken &usd_source_name, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + const int column, + NodePlacementContext *r_ctx) const +{ + if (!usd_shader || !dest_node || !ntree || !dest_socket_name || !bmain_ || !r_ctx) { + return; + } + + float locx = 0.0f; + float locy = 0.0f; + compute_node_loc(column, &locx, &locy, r_ctx); + + /* Create the Texture Image node. */ + bNode *tex_image = add_node(nullptr, ntree, SH_NODE_TEX_IMAGE, locx, locy); + + if (!tex_image) { + std::cerr << "ERROR: Couldn't create SH_NODE_TEX_IMAGE for node input " << dest_socket_name + << std::endl; + return; + } + + /* Load the texture image. */ + load_tex_image(usd_shader, tex_image); + + /* Connect to destination node input. */ + + /* Get the source socket name. */ + std::string source_socket_name = usd_source_name == usdtokens::a ? "Alpha" : "Color"; + + link_nodes(ntree, tex_image, source_socket_name.c_str(), dest_node, dest_socket_name); + + /* Connect the texture image node "Vector" input. */ + if (pxr::UsdShadeInput st_input = usd_shader.GetInput(usdtokens::st)) { + set_node_input(st_input, tex_image, "Vector", ntree, column, r_ctx); + } +} + +/* Load the texture image node's texture from the path given by the USD shader's + * file input value. */ +void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader, + bNode *tex_image) const +{ + if (!(usd_shader && tex_image && tex_image->type == SH_NODE_TEX_IMAGE)) { + return; + } + + /* Try to load the texture image. */ + pxr::UsdShadeInput file_input = usd_shader.GetInput(usdtokens::file); + + if (!file_input) { + std::cerr << "WARNING: Couldn't get file input for USD shader " << usd_shader.GetPath() + << std::endl; + return; + } + + pxr::VtValue file_val; + if (!file_input.Get(&file_val) || !file_val.IsHolding<pxr::SdfAssetPath>()) { + std::cerr << "WARNING: Couldn't get file input value for USD shader " << usd_shader.GetPath() + << std::endl; + return; + } + + const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>(); + std::string file_path = asset_path.GetResolvedPath(); + if (file_path.empty()) { + std::cerr << "WARNING: Couldn't resolve image asset '" << asset_path + << "' for Texture Image node." << std::endl; + return; + } + + const char *im_file = file_path.c_str(); + Image *image = BKE_image_load_exists(bmain_, im_file); + if (!image) { + std::cerr << "WARNING: Couldn't open image file '" << im_file << "' for Texture Image node." + << std::endl; + return; + } + + tex_image->id = &image->id; + + /* Set texture color space. + * TODO(makowalski): For now, just checking for RAW color space, + * assuming sRGB otherwise, but more complex logic might be + * required if the color space is "auto". */ + + pxr::TfToken color_space = get_source_color_space(usd_shader); + + if (color_space.IsEmpty()) { + color_space = file_input.GetAttr().GetColorSpace(); + } + + if (color_space == usdtokens::RAW || color_space == usdtokens::raw) { + STRNCPY(image->colorspace_settings.name, "Raw"); + } +} + +/* This function creates a Blender UV Map node, under the simplifying assumption that + * UsdPrimvarReader_float2 shaders output UV coordinates. + * TODO(makowalski): investigate supporting conversion to other Blender node types + * (e.g., Attribute Nodes) if needed. */ +void USDMaterialReader::convert_usd_primvar_reader_float2( + const pxr::UsdShadeShader &usd_shader, + const pxr::TfToken & /* usd_source_name */, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + const int column, + NodePlacementContext *r_ctx) const +{ + if (!usd_shader || !dest_node || !ntree || !dest_socket_name || !bmain_ || !r_ctx) { + return; + } + + float locx = 0.0f; + float locy = 0.0f; + compute_node_loc(column, &locx, &locy, r_ctx); + + /* Create the UV Map node. */ + bNode *uv_map = add_node(nullptr, ntree, SH_NODE_UVMAP, locx, locy); + + if (!uv_map) { + std::cerr << "ERROR: Couldn't create SH_NODE_UVMAP for node input " << dest_socket_name + << std::endl; + return; + } + + /* Set the texmap name. */ + pxr::UsdShadeInput varname_input = usd_shader.GetInput(usdtokens::varname); + if (varname_input) { + pxr::VtValue varname_val; + if (varname_input.Get(&varname_val) && varname_val.IsHolding<pxr::TfToken>()) { + std::string varname = varname_val.Get<pxr::TfToken>().GetString(); + if (!varname.empty()) { + NodeShaderUVMap *storage = (NodeShaderUVMap *)uv_map->storage; + BLI_strncpy(storage->uv_map, varname.c_str(), sizeof(storage->uv_map)); + } + } + } + + /* Connect to destination node input. */ + link_nodes(ntree, uv_map, "UV", dest_node, dest_socket_name); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_material.h b/source/blender/io/usd/intern/usd_reader_material.h new file mode 100644 index 00000000000..a17504bd590 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_material.h @@ -0,0 +1,132 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 NVIDIA Corporation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" + +#include <pxr/usd/usdShade/material.h> + +struct Main; +struct Material; +struct bNode; +struct bNodeTree; + +namespace blender::io::usd { + +/* Helper struct used when arranging nodes in columns, keeping track the + * occupancy information for a given column. I.e., for column n, + * column_offsets[n] is the y-offset (from top to bottom) of the occupied + * region in that column. */ +struct NodePlacementContext { + float origx; + float origy; + std::vector<float> column_offsets; + const float horizontal_step; + const float vertical_step; + + NodePlacementContext(float in_origx, + float in_origy, + float in_horizontal_step = 300.0f, + float in_vertical_step = 300.0f) + : origx(in_origx), + origy(in_origy), + column_offsets(64, 0.0f), + horizontal_step(in_horizontal_step), + vertical_step(in_vertical_step) + { + } +}; + +/* Converts USD materials to Blender representation. */ + +/** + By default, the #USDMaterialReader creates a Blender material with + * the same name as the USD material. If the USD material has a + * #UsdPreviewSurface source, the Blender material's viewport display + * color, roughness and metallic properties are set to the corresponding + * #UsdPreoviewSurface inputs. + * + * If the Import USD Preview option is enabled, the current implementation + * converts #UsdPreviewSurface to Blender nodes as follows: + * + * - #UsdPreviewSurface -> Principled BSDF + * - #UsdUVTexture -> Texture Image + Normal Map + * - UsdPrimvarReader_float2 -> UV Map + * + * Limitations: arbitrary primvar readers or UsdTransform2d not yet + * supported. For #UsdUVTexture, only the file, st and #sourceColorSpace + * inputs are handled. + * + * TODO(makowalski): Investigate adding support for converting additional + * shaders and inputs. Supporting certain types of inputs, such as texture + * scale and bias, will probably require creating Blender Group nodes with + * the corresponding inputs. + */ +class USDMaterialReader { + protected: + USDImportParams params_; + + Main *bmain_; + + public: + USDMaterialReader(const USDImportParams ¶ms, Main *bmain); + + Material *add_material(const pxr::UsdShadeMaterial &usd_material) const; + + protected: + void import_usd_preview(Material *mtl, const pxr::UsdShadeShader &usd_shader) const; + + void set_principled_node_inputs(bNode *principled_node, + bNodeTree *ntree, + const pxr::UsdShadeShader &usd_shader) const; + + void set_node_input(const pxr::UsdShadeInput &usd_input, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + int column, + NodePlacementContext *r_ctx) const; + + void follow_connection(const pxr::UsdShadeInput &usd_input, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + int column, + NodePlacementContext *r_ctx) const; + + void convert_usd_uv_texture(const pxr::UsdShadeShader &usd_shader, + const pxr::TfToken &usd_source_name, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + int column, + NodePlacementContext *r_ctx) const; + + void load_tex_image(const pxr::UsdShadeShader &usd_shader, bNode *tex_image) const; + + void convert_usd_primvar_reader_float2(const pxr::UsdShadeShader &usd_shader, + const pxr::TfToken &usd_source_name, + bNode *dest_node, + const char *dest_socket_name, + bNodeTree *ntree, + int column, + NodePlacementContext *r_ctx) const; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc new file mode 100644 index 00000000000..9c75bc8afae --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -0,0 +1,853 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation and + * NVIDIA Corporation. All rights reserved. + */ + +#include "usd_reader_mesh.h" +#include "usd_reader_material.h" + +#include "BKE_customdata.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_object.h" + +#include "BLI_math.h" +#include "BLI_math_geom.h" +#include "BLI_string.h" + +#include "DNA_customdata_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" + +#include "MEM_guardedalloc.h" + +#include <pxr/base/vt/array.h> +#include <pxr/base/vt/types.h> +#include <pxr/base/vt/value.h> +#include <pxr/usd/sdf/types.h> +#include <pxr/usd/usdGeom/mesh.h> +#include <pxr/usd/usdGeom/subset.h> +#include <pxr/usd/usdShade/materialBindingAPI.h> + +#include <iostream> + +namespace usdtokens { +/* Materials */ +static const pxr::TfToken st("st", pxr::TfToken::Immortal); +static const pxr::TfToken UVMap("UVMap", pxr::TfToken::Immortal); +static const pxr::TfToken Cd("Cd", pxr::TfToken::Immortal); +static const pxr::TfToken displayColor("displayColor", pxr::TfToken::Immortal); +static const pxr::TfToken normalsPrimvar("normals", pxr::TfToken::Immortal); +} // namespace usdtokens + +namespace utils { +/* Very similar to #blender::io::alembic::utils. */ +static void build_mat_map(const Main *bmain, std::map<std::string, Material *> *r_mat_map) +{ + if (r_mat_map == nullptr) { + return; + } + + Material *material = static_cast<Material *>(bmain->materials.first); + + for (; material; material = static_cast<Material *>(material->id.next)) { + /* We have to do this because the stored material name is coming directly from USD. */ + (*r_mat_map)[pxr::TfMakeValidIdentifier(material->id.name + 2)] = material; + } +} + +static void assign_materials(Main *bmain, + Object *ob, + const std::map<pxr::SdfPath, int> &mat_index_map, + const USDImportParams ¶ms, + pxr::UsdStageRefPtr stage) +{ + if (!(stage && bmain && ob)) { + return; + } + + bool can_assign = true; + std::map<pxr::SdfPath, int>::const_iterator it = mat_index_map.begin(); + + int matcount = 0; + for (; it != mat_index_map.end(); ++it, matcount++) { + if (!BKE_object_material_slot_add(bmain, ob)) { + can_assign = false; + break; + } + } + + if (!can_assign) { + return; + } + + /* TODO(kevin): use global map? */ + std::map<std::string, Material *> mat_map; + build_mat_map(bmain, &mat_map); + + blender::io::usd::USDMaterialReader mat_reader(params, bmain); + + for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) { + std::string mat_name = it->first.GetName(); + + std::map<std::string, Material *>::iterator mat_iter = mat_map.find(mat_name); + + Material *assigned_mat = nullptr; + + if (mat_iter == mat_map.end()) { + /* Blender material doesn't exist, so create it now. */ + + /* Look up the USD material. */ + pxr::UsdPrim prim = stage->GetPrimAtPath(it->first); + pxr::UsdShadeMaterial usd_mat(prim); + + if (!usd_mat) { + std::cout << "WARNING: Couldn't construct USD material from prim " << it->first + << std::endl; + continue; + } + + /* Add the Blender material. */ + assigned_mat = mat_reader.add_material(usd_mat); + + if (!assigned_mat) { + std::cout << "WARNING: Couldn't create Blender material from USD material " << it->first + << std::endl; + continue; + } + + mat_map[mat_name] = assigned_mat; + } + else { + /* We found an existing Blender material. */ + assigned_mat = mat_iter->second; + } + + if (assigned_mat) { + BKE_object_material_assign(bmain, ob, assigned_mat, it->second, BKE_MAT_ASSIGN_OBDATA); + } + else { + /* This shouldn't happen. */ + std::cout << "WARNING: Couldn't assign material " << mat_name << std::endl; + } + } +} + +} // namespace utils + +static void *add_customdata_cb(Mesh *mesh, const char *name, const int data_type) +{ + CustomDataType cd_data_type = static_cast<CustomDataType>(data_type); + void *cd_ptr; + CustomData *loopdata; + int numloops; + + /* unsupported custom data type -- don't do anything. */ + if (!ELEM(cd_data_type, CD_MLOOPUV, CD_MLOOPCOL)) { + return nullptr; + } + + loopdata = &mesh->ldata; + cd_ptr = CustomData_get_layer_named(loopdata, cd_data_type, name); + if (cd_ptr != nullptr) { + /* layer already exists, so just return it. */ + return cd_ptr; + } + + /* Create a new layer. */ + numloops = mesh->totloop; + cd_ptr = CustomData_add_layer_named(loopdata, cd_data_type, CD_DEFAULT, nullptr, numloops, name); + return cd_ptr; +} + +namespace blender::io::usd { + +USDMeshReader::USDMeshReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDGeomReader(prim, import_params, settings), + mesh_prim_(prim), + is_left_handed_(false), + has_uvs_(false), + is_time_varying_(false), + is_initial_load_(false) +{ +} + +void USDMeshReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + Mesh *mesh = BKE_mesh_add(bmain, name_.c_str()); + + object_ = BKE_object_add_only_object(bmain, OB_MESH, name_.c_str()); + object_->data = mesh; +} + +void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime) +{ + Mesh *mesh = (Mesh *)object_->data; + + is_initial_load_ = true; + Mesh *read_mesh = this->read_mesh( + mesh, motionSampleTime, import_params_.mesh_read_flag, nullptr); + + is_initial_load_ = false; + if (read_mesh != mesh) { + /* FIXME: after 2.80; `mesh->flag` isn't copied by #BKE_mesh_nomain_to_mesh() */ + /* read_mesh can be freed by BKE_mesh_nomain_to_mesh(), so get the flag before that happens. */ + short autosmooth = (read_mesh->flag & ME_AUTOSMOOTH); + BKE_mesh_nomain_to_mesh(read_mesh, mesh, object_, &CD_MASK_MESH, true); + mesh->flag |= autosmooth; + } + + readFaceSetsSample(bmain, mesh, motionSampleTime); + + if (mesh_prim_.GetPointsAttr().ValueMightBeTimeVarying()) { + is_time_varying_ = true; + } + + if (is_time_varying_) { + add_cache_modifier(); + } + + if (import_params_.import_subdiv) { + pxr::TfToken subdivScheme; + mesh_prim_.GetSubdivisionSchemeAttr().Get(&subdivScheme, motionSampleTime); + + if (subdivScheme == pxr::UsdGeomTokens->catmullClark) { + add_subdiv_modifier(); + } + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +bool USDMeshReader::valid() const +{ + return static_cast<bool>(mesh_prim_); +} + +bool USDMeshReader::topology_changed(Mesh *existing_mesh, const double motionSampleTime) +{ + /* TODO(makowalski): Is it the best strategy to cache the mesh + * geometry in this function? This needs to be revisited. */ + + mesh_prim_.GetFaceVertexIndicesAttr().Get(&face_indices_, motionSampleTime); + mesh_prim_.GetFaceVertexCountsAttr().Get(&face_counts_, motionSampleTime); + mesh_prim_.GetPointsAttr().Get(&positions_, motionSampleTime); + + /* TODO(makowalski): Reading normals probably doesn't belong in this function, + * as this is not required to determine if the topology has changed. */ + + /* If 'normals' and 'primvars:normals' are both specified, the latter has precedence. */ + pxr::UsdGeomPrimvar primvar = mesh_prim_.GetPrimvar(usdtokens::normalsPrimvar); + if (primvar.HasValue()) { + primvar.ComputeFlattened(&normals_, motionSampleTime); + normal_interpolation_ = primvar.GetInterpolation(); + } + else { + mesh_prim_.GetNormalsAttr().Get(&normals_, motionSampleTime); + normal_interpolation_ = mesh_prim_.GetNormalsInterpolation(); + } + + return positions_.size() != existing_mesh->totvert || + face_counts_.size() != existing_mesh->totpoly || + face_indices_.size() != existing_mesh->totloop; +} + +void USDMeshReader::read_mpolys(Mesh *mesh) +{ + MPoly *mpolys = mesh->mpoly; + MLoop *mloops = mesh->mloop; + + int loop_index = 0; + + for (int i = 0; i < face_counts_.size(); i++) { + const int face_size = face_counts_[i]; + + MPoly &poly = mpolys[i]; + poly.loopstart = loop_index; + poly.totloop = face_size; + poly.mat_nr = 0; + + /* Polygons are always assumed to be smooth-shaded. If the mesh should be flat-shaded, + * this is encoded in custom loop normals. */ + poly.flag |= ME_SMOOTH; + + if (is_left_handed_) { + int loop_end_index = loop_index + (face_size - 1); + for (int f = 0; f < face_size; ++f, ++loop_index) { + mloops[loop_index].v = face_indices_[loop_end_index - f]; + } + } + else { + for (int f = 0; f < face_size; ++f, ++loop_index) { + mloops[loop_index].v = face_indices_[loop_index]; + } + } + } + + BKE_mesh_calc_edges(mesh, false, false); +} + +void USDMeshReader::read_uvs(Mesh *mesh, const double motionSampleTime, const bool load_uvs) +{ + unsigned int loop_index = 0; + unsigned int rev_loop_index = 0; + unsigned int uv_index = 0; + + const CustomData *ldata = &mesh->ldata; + + struct UVSample { + pxr::VtVec2fArray uvs; + pxr::TfToken interpolation; + }; + + std::vector<UVSample> uv_primvars(ldata->totlayer); + + if (has_uvs_) { + for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) { + const CustomDataLayer *layer = &ldata->layers[layer_idx]; + std::string layer_name = std::string(layer->name); + if (layer->type != CD_MLOOPUV) { + continue; + } + + pxr::TfToken uv_token; + + /* If first time seeing uv token, store in map of `<layer->uid, TfToken>`. */ + if (uv_token_map_.find(layer_name) == uv_token_map_.end()) { + uv_token = pxr::TfToken(layer_name); + uv_token_map_.insert(std::make_pair(layer_name, uv_token)); + } + else { + uv_token = uv_token_map_.at(layer_name); + } + + /* Early out if no token found, this should never happen */ + if (uv_token.IsEmpty()) { + continue; + } + /* Early out if not first load and UVs aren't animated. */ + if (!load_uvs && primvar_varying_map_.find(uv_token) != primvar_varying_map_.end() && + !primvar_varying_map_.at(uv_token)) { + continue; + } + + /* Early out if mesh doesn't have primvar. */ + if (!mesh_prim_.HasPrimvar(uv_token)) { + continue; + } + + if (pxr::UsdGeomPrimvar uv_primvar = mesh_prim_.GetPrimvar(uv_token)) { + uv_primvar.ComputeFlattened(&uv_primvars[layer_idx].uvs, motionSampleTime); + uv_primvars[layer_idx].interpolation = uv_primvar.GetInterpolation(); + } + } + } + + for (int i = 0; i < face_counts_.size(); i++) { + const int face_size = face_counts_[i]; + + rev_loop_index = loop_index + (face_size - 1); + + for (int f = 0; f < face_size; f++, loop_index++, rev_loop_index--) { + + for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) { + const CustomDataLayer *layer = &ldata->layers[layer_idx]; + if (layer->type != CD_MLOOPUV) { + continue; + } + + /* Early out if mismatched layer sizes. */ + if (layer_idx > uv_primvars.size()) { + continue; + } + + /* Early out if no uvs loaded. */ + if (uv_primvars[layer_idx].uvs.empty()) { + continue; + } + + const UVSample &sample = uv_primvars[layer_idx]; + + if (!(sample.interpolation == pxr::UsdGeomTokens->faceVarying || + sample.interpolation == pxr::UsdGeomTokens->vertex)) { + std::cerr << "WARNING: unexpected interpolation type " << sample.interpolation + << " for uv " << layer->name << std::endl; + continue; + } + + /* For Vertex interpolation, use the vertex index. */ + int usd_uv_index = sample.interpolation == pxr::UsdGeomTokens->vertex ? + mesh->mloop[loop_index].v : + loop_index; + + if (usd_uv_index >= sample.uvs.size()) { + std::cerr << "WARNING: out of bounds uv index " << usd_uv_index << " for uv " + << layer->name << " of size " << sample.uvs.size() << std::endl; + continue; + } + + MLoopUV *mloopuv = static_cast<MLoopUV *>(layer->data); + if (is_left_handed_) { + uv_index = rev_loop_index; + } + else { + uv_index = loop_index; + } + mloopuv[uv_index].uv[0] = sample.uvs[usd_uv_index][0]; + mloopuv[uv_index].uv[1] = sample.uvs[usd_uv_index][1]; + } + } + } +} + +void USDMeshReader::read_colors(Mesh *mesh, const double motionSampleTime) +{ + if (!(mesh && mesh_prim_ && mesh->totloop > 0)) { + return; + } + + /* Early out if we read the display color before and if this attribute isn't animated. */ + if (primvar_varying_map_.find(usdtokens::displayColor) != primvar_varying_map_.end() && + !primvar_varying_map_.at(usdtokens::displayColor)) { + return; + } + + pxr::UsdGeomPrimvar color_primvar = mesh_prim_.GetDisplayColorPrimvar(); + + if (!color_primvar.HasValue()) { + return; + } + + pxr::TfToken interp = color_primvar.GetInterpolation(); + + if (interp == pxr::UsdGeomTokens->varying) { + std::cerr << "WARNING: Unsupported varying interpolation for display colors\n" << std::endl; + return; + } + + if (primvar_varying_map_.find(usdtokens::displayColor) == primvar_varying_map_.end()) { + bool might_be_time_varying = color_primvar.ValueMightBeTimeVarying(); + primvar_varying_map_.insert(std::make_pair(usdtokens::displayColor, might_be_time_varying)); + if (might_be_time_varying) { + is_time_varying_ = true; + } + } + + pxr::VtArray<pxr::GfVec3f> display_colors; + + if (!color_primvar.ComputeFlattened(&display_colors, motionSampleTime)) { + std::cerr << "WARNING: Couldn't compute display colors\n" << std::endl; + return; + } + + if ((interp == pxr::UsdGeomTokens->faceVarying && display_colors.size() != mesh->totloop) || + (interp == pxr::UsdGeomTokens->vertex && display_colors.size() != mesh->totvert) || + (interp == pxr::UsdGeomTokens->constant && display_colors.size() != 1) || + (interp == pxr::UsdGeomTokens->uniform && display_colors.size() != mesh->totpoly)) { + std::cerr << "WARNING: display colors count mismatch\n" << std::endl; + return; + } + + void *cd_ptr = add_customdata_cb(mesh, "displayColors", CD_MLOOPCOL); + + if (!cd_ptr) { + std::cerr << "WARNING: Couldn't add displayColors custom data.\n"; + return; + } + + MLoopCol *colors = static_cast<MLoopCol *>(cd_ptr); + + mesh->mloopcol = colors; + + MPoly *poly = mesh->mpoly; + + for (int i = 0, e = mesh->totpoly; i < e; ++i, ++poly) { + for (int j = 0; j < poly->totloop; ++j) { + int loop_index = poly->loopstart + j; + + /* Default for constant varying interpolation. */ + int usd_index = 0; + + if (interp == pxr::UsdGeomTokens->vertex) { + usd_index = mesh->mloop[loop_index].v; + } + else if (interp == pxr::UsdGeomTokens->faceVarying) { + usd_index = poly->loopstart; + if (is_left_handed_) { + usd_index += poly->totloop - 1 - j; + } + else { + usd_index += j; + } + } + else if (interp == pxr::UsdGeomTokens->uniform) { + /* Uniform varying uses the poly index. */ + usd_index = i; + } + + if (usd_index >= display_colors.size()) { + continue; + } + + colors[loop_index].r = unit_float_to_uchar_clamp(display_colors[usd_index][0]); + colors[loop_index].g = unit_float_to_uchar_clamp(display_colors[usd_index][1]); + colors[loop_index].b = unit_float_to_uchar_clamp(display_colors[usd_index][2]); + colors[loop_index].a = unit_float_to_uchar_clamp(1.0); + } + } +} + +void USDMeshReader::process_normals_vertex_varying(Mesh *mesh) +{ + if (!mesh) { + return; + } + + if (normals_.empty()) { + BKE_mesh_calc_normals(mesh); + return; + } + + if (normals_.size() != mesh->totvert) { + std::cerr << "WARNING: vertex varying normals count mismatch for mesh " << prim_path_ + << std::endl; + BKE_mesh_calc_normals(mesh); + return; + } + + for (int i = 0; i < normals_.size(); i++) { + MVert &mvert = mesh->mvert[i]; + normal_float_to_short_v3(mvert.no, normals_[i].data()); + } +} + +void USDMeshReader::process_normals_face_varying(Mesh *mesh) +{ + if (normals_.empty()) { + BKE_mesh_calc_normals(mesh); + return; + } + + /* Check for normals count mismatches to prevent crashes. */ + if (normals_.size() != mesh->totloop) { + std::cerr << "WARNING: loop normal count mismatch for mesh " << mesh->id.name << std::endl; + BKE_mesh_calc_normals(mesh); + return; + } + + mesh->flag |= ME_AUTOSMOOTH; + + long int loop_count = normals_.size(); + + float(*lnors)[3] = static_cast<float(*)[3]>( + MEM_malloc_arrayN(loop_count, sizeof(float[3]), "USD::FaceNormals")); + + MPoly *mpoly = mesh->mpoly; + + for (int i = 0, e = mesh->totpoly; i < e; ++i, ++mpoly) { + for (int j = 0; j < mpoly->totloop; j++) { + int blender_index = mpoly->loopstart + j; + + int usd_index = mpoly->loopstart; + if (is_left_handed_) { + usd_index += mpoly->totloop - 1 - j; + } + else { + usd_index += j; + } + + lnors[blender_index][0] = normals_[usd_index][0]; + lnors[blender_index][1] = normals_[usd_index][1]; + lnors[blender_index][2] = normals_[usd_index][2]; + } + } + BKE_mesh_set_custom_normals(mesh, lnors); + + MEM_freeN(lnors); +} + +/* Set USD uniform (per-face) normals as Blender loop normals. */ +void USDMeshReader::process_normals_uniform(Mesh *mesh) +{ + if (normals_.empty()) { + BKE_mesh_calc_normals(mesh); + return; + } + + /* Check for normals count mismatches to prevent crashes. */ + if (normals_.size() != mesh->totpoly) { + std::cerr << "WARNING: uniform normal count mismatch for mesh " << mesh->id.name << std::endl; + BKE_mesh_calc_normals(mesh); + return; + } + + float(*lnors)[3] = static_cast<float(*)[3]>( + MEM_malloc_arrayN(mesh->totloop, sizeof(float[3]), "USD::FaceNormals")); + + MPoly *mpoly = mesh->mpoly; + + for (int i = 0, e = mesh->totpoly; i < e; ++i, ++mpoly) { + + for (int j = 0; j < mpoly->totloop; j++) { + int loop_index = mpoly->loopstart + j; + lnors[loop_index][0] = normals_[i][0]; + lnors[loop_index][1] = normals_[i][1]; + lnors[loop_index][2] = normals_[i][2]; + } + } + + mesh->flag |= ME_AUTOSMOOTH; + BKE_mesh_set_custom_normals(mesh, lnors); + + MEM_freeN(lnors); +} + +void USDMeshReader::read_mesh_sample(ImportSettings *settings, + Mesh *mesh, + const double motionSampleTime, + const bool new_mesh) +{ + /* Note that for new meshes we always want to read verts and polys, + * regardless of the value of the read_flag, to avoid a crash downstream + * in code that expect this data to be there. */ + + if (new_mesh || (settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { + for (int i = 0; i < positions_.size(); i++) { + MVert &mvert = mesh->mvert[i]; + mvert.co[0] = positions_[i][0]; + mvert.co[1] = positions_[i][1]; + mvert.co[2] = positions_[i][2]; + } + } + + if (new_mesh || (settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) { + read_mpolys(mesh); + if (normal_interpolation_ == pxr::UsdGeomTokens->faceVarying) { + process_normals_face_varying(mesh); + } + else if (normal_interpolation_ == pxr::UsdGeomTokens->uniform) { + process_normals_uniform(mesh); + } + else { + /* Default */ + BKE_mesh_calc_normals(mesh); + } + } + + /* Process point normals after reading polys. This + * is important in the case where the normals are empty + * and we invoke BKE_mesh_calc_normals(mesh), which requires + * edges to be defined. */ + if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0 && + normal_interpolation_ == pxr::UsdGeomTokens->vertex) { + process_normals_vertex_varying(mesh); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) { + read_uvs(mesh, motionSampleTime, new_mesh); + } + + if ((settings->read_flag & MOD_MESHSEQ_READ_COLOR) != 0) { + read_colors(mesh, motionSampleTime); + } +} + +void USDMeshReader::assign_facesets_to_mpoly(double motionSampleTime, + MPoly *mpoly, + const int /* totpoly */, + std::map<pxr::SdfPath, int> *r_mat_map) +{ + if (r_mat_map == nullptr) { + return; + } + + /* Find the geom subsets that have bound materials. + * We don't call #pxr::UsdShadeMaterialBindingAPI::GetMaterialBindSubsets() + * because this function returns only those subsets that are in the 'materialBind' + * family, but, in practice, applications (like Houdini) might export subsets + * in different families that are bound to materials. + * TODO(makowalski): Reassess if the above is the best approach. */ + const std::vector<pxr::UsdGeomSubset> subsets = pxr::UsdGeomSubset::GetAllGeomSubsets( + mesh_prim_); + + int current_mat = 0; + if (!subsets.empty()) { + for (const pxr::UsdGeomSubset &subset : subsets) { + pxr::UsdShadeMaterialBindingAPI subset_api = pxr::UsdShadeMaterialBindingAPI( + subset.GetPrim()); + + pxr::UsdShadeMaterial subset_mtl = subset_api.ComputeBoundMaterial(); + + if (!subset_mtl) { + continue; + } + + pxr::SdfPath subset_mtl_path = subset_mtl.GetPath(); + + if (subset_mtl_path.IsEmpty()) { + continue; + } + + if (r_mat_map->find(subset_mtl_path) == r_mat_map->end()) { + (*r_mat_map)[subset_mtl_path] = 1 + current_mat++; + } + + const int mat_idx = (*r_mat_map)[subset_mtl_path] - 1; + + pxr::UsdAttribute indicesAttribute = subset.GetIndicesAttr(); + pxr::VtIntArray indices; + indicesAttribute.Get(&indices, motionSampleTime); + + for (int i = 0; i < indices.size(); i++) { + MPoly &poly = mpoly[indices[i]]; + poly.mat_nr = mat_idx; + } + } + } + + if (r_mat_map->empty()) { + pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(prim_); + + if (pxr::UsdShadeMaterial mtl = api.ComputeBoundMaterial()) { + + pxr::SdfPath mtl_path = mtl.GetPath(); + + if (!mtl_path.IsEmpty()) { + r_mat_map->insert(std::make_pair(mtl.GetPath(), 1)); + } + } + } +} + +void USDMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const double motionSampleTime) +{ + if (!import_params_.import_materials) { + return; + } + + std::map<pxr::SdfPath, int> mat_map; + assign_facesets_to_mpoly(motionSampleTime, mesh->mpoly, mesh->totpoly, &mat_map); + utils::assign_materials(bmain, object_, mat_map, this->import_params_, this->prim_.GetStage()); +} + +Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh, + const double motionSampleTime, + const int read_flag, + const char ** /* err_str */) +{ + if (!mesh_prim_) { + return existing_mesh; + } + + mesh_prim_.GetOrientationAttr().Get(&orientation_); + if (orientation_ == pxr::UsdGeomTokens->leftHanded) { + is_left_handed_ = true; + } + + std::vector<pxr::TfToken> uv_tokens; + + /* Currently we only handle UV primvars. */ + if (read_flag & MOD_MESHSEQ_READ_UV) { + + std::vector<pxr::UsdGeomPrimvar> primvars = mesh_prim_.GetPrimvars(); + + for (pxr::UsdGeomPrimvar p : primvars) { + + pxr::TfToken name = p.GetPrimvarName(); + pxr::SdfValueTypeName type = p.GetTypeName(); + + bool is_uv = false; + + /* Assume all UVs are stored in one of these primvar types */ + if (type == pxr::SdfValueTypeNames->TexCoord2hArray || + type == pxr::SdfValueTypeNames->TexCoord2fArray || + type == pxr::SdfValueTypeNames->TexCoord2dArray) { + is_uv = true; + } + /* In some cases, the st primvar is stored as float2 values. */ + else if (name == usdtokens::st && type == pxr::SdfValueTypeNames->Float2Array) { + is_uv = true; + } + + if (is_uv) { + + pxr::TfToken interp = p.GetInterpolation(); + + if (!(interp == pxr::UsdGeomTokens->faceVarying || interp == pxr::UsdGeomTokens->vertex)) { + continue; + } + + uv_tokens.push_back(p.GetBaseName()); + has_uvs_ = true; + + /* Record whether the UVs might be time varying. */ + if (primvar_varying_map_.find(name) == primvar_varying_map_.end()) { + bool might_be_time_varying = p.ValueMightBeTimeVarying(); + primvar_varying_map_.insert(std::make_pair(name, might_be_time_varying)); + if (might_be_time_varying) { + is_time_varying_ = true; + } + } + } + } + } + + Mesh *active_mesh = existing_mesh; + bool new_mesh = false; + + /* TODO(makowalski): implement the optimization of only updating the mesh points when + * the topology is consistent, as in the Alembic importer. */ + + ImportSettings settings; + settings.read_flag |= read_flag; + + if (topology_changed(existing_mesh, motionSampleTime)) { + new_mesh = true; + active_mesh = BKE_mesh_new_nomain_from_template( + existing_mesh, positions_.size(), 0, 0, face_indices_.size(), face_counts_.size()); + + for (pxr::TfToken token : uv_tokens) { + void *cd_ptr = add_customdata_cb(active_mesh, token.GetText(), CD_MLOOPUV); + active_mesh->mloopuv = static_cast<MLoopUV *>(cd_ptr); + } + } + + read_mesh_sample(&settings, active_mesh, motionSampleTime, new_mesh || is_initial_load_); + + if (new_mesh) { + /* Here we assume that the number of materials doesn't change, i.e. that + * the material slots that were created when the object was loaded from + * USD are still valid now. */ + size_t num_polys = active_mesh->totpoly; + if (num_polys > 0 && import_params_.import_materials) { + std::map<pxr::SdfPath, int> mat_map; + assign_facesets_to_mpoly(motionSampleTime, active_mesh->mpoly, num_polys, &mat_map); + } + } + + return active_mesh; +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_mesh.h b/source/blender/io/usd/intern/usd_reader_mesh.h new file mode 100644 index 00000000000..54ad144d191 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_mesh.h @@ -0,0 +1,95 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation and + * NVIDIA Corporation. All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_geom.h" + +#include "pxr/usd/usdGeom/mesh.h" + +struct MPoly; + +namespace blender::io::usd { + +class USDMeshReader : public USDGeomReader { + private: + pxr::UsdGeomMesh mesh_prim_; + + std::unordered_map<std::string, pxr::TfToken> uv_token_map_; + std::map<const pxr::TfToken, bool> primvar_varying_map_; + + /* TODO(makowalski): Is it the best strategy to cache the + * mesh geometry in the following members? It appears these + * arrays are never cleared, so this might bloat memory. */ + pxr::VtIntArray face_indices_; + pxr::VtIntArray face_counts_; + pxr::VtVec3fArray positions_; + pxr::VtVec3fArray normals_; + + pxr::TfToken normal_interpolation_; + pxr::TfToken orientation_; + bool is_left_handed_; + bool has_uvs_; + bool is_time_varying_; + + /* This is to ensure we load all data once, because we reuse the read_mesh function + * in the mesh seq modifier, and in initial load. Ideally, a better fix would be + * implemented. Note this will break if faces or positions vary. */ + bool is_initial_load_; + + public: + USDMeshReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings); + + bool valid() const override; + + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; + + struct Mesh *read_mesh(struct Mesh *existing_mesh, + double motionSampleTime, + int read_flag, + const char **err_str) override; + + bool topology_changed(Mesh *existing_mesh, double motionSampleTime) override; + + private: + void process_normals_vertex_varying(Mesh *mesh); + void process_normals_face_varying(Mesh *mesh); + void process_normals_uniform(Mesh *mesh); + void readFaceSetsSample(Main *bmain, Mesh *mesh, double motionSampleTime); + void assign_facesets_to_mpoly(double motionSampleTime, + struct MPoly *mpoly, + int totpoly, + std::map<pxr::SdfPath, int> *r_mat_map); + + void read_mpolys(Mesh *mesh); + void read_uvs(Mesh *mesh, double motionSampleTime, bool load_uvs = false); + void read_colors(Mesh *mesh, double motionSampleTime); + + void read_mesh_sample(ImportSettings *settings, + Mesh *mesh, + double motionSampleTime, + bool new_mesh); +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_nurbs.cc b/source/blender/io/usd/intern/usd_reader_nurbs.cc new file mode 100644 index 00000000000..9b30b524729 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_nurbs.cc @@ -0,0 +1,256 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_nurbs.h" + +#include "BKE_curve.h" +#include "BKE_mesh.h" +#include "BKE_object.h" + +#include "BLI_listbase.h" + +#include "DNA_curve_types.h" +#include "DNA_object_types.h" + +#include "MEM_guardedalloc.h" + +#include <pxr/base/vt/array.h> +#include <pxr/base/vt/types.h> +#include <pxr/base/vt/value.h> +#include <pxr/usd/sdf/types.h> + +#include <pxr/usd/usdGeom/curves.h> + +static bool set_knots(const pxr::VtDoubleArray &knots, float *&nu_knots) +{ + if (knots.empty()) { + return false; + } + + /* Skip first and last knots, as they are used for padding. */ + const size_t num_knots = knots.size(); + nu_knots = static_cast<float *>(MEM_callocN(num_knots * sizeof(float), __func__)); + + for (size_t i = 0; i < num_knots; i++) { + nu_knots[i] = (float)knots[i]; + } + + return true; +} + +namespace blender::io::usd { + +void USDNurbsReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + curve_ = BKE_curve_add(bmain, name_.c_str(), OB_CURVE); + + curve_->flag |= CU_DEFORM_FILL | CU_3D; + curve_->actvert = CU_ACT_NONE; + curve_->resolu = 2; + + object_ = BKE_object_add_only_object(bmain, OB_CURVE, name_.c_str()); + object_->data = curve_; +} + +void USDNurbsReader::read_object_data(Main *bmain, const double motionSampleTime) +{ + Curve *cu = (Curve *)object_->data; + read_curve_sample(cu, motionSampleTime); + + if (curve_prim_.GetPointsAttr().ValueMightBeTimeVarying()) { + add_cache_modifier(); + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +void USDNurbsReader::read_curve_sample(Curve *cu, const double motionSampleTime) +{ + curve_prim_ = pxr::UsdGeomNurbsCurves(prim_); + + pxr::UsdAttribute widthsAttr = curve_prim_.GetWidthsAttr(); + pxr::UsdAttribute vertexAttr = curve_prim_.GetCurveVertexCountsAttr(); + pxr::UsdAttribute pointsAttr = curve_prim_.GetPointsAttr(); + + pxr::VtIntArray usdCounts; + vertexAttr.Get(&usdCounts, motionSampleTime); + + pxr::VtVec3fArray usdPoints; + pointsAttr.Get(&usdPoints, motionSampleTime); + + pxr::VtFloatArray usdWidths; + widthsAttr.Get(&usdWidths, motionSampleTime); + + pxr::VtIntArray orders; + curve_prim_.GetOrderAttr().Get(&orders, motionSampleTime); + + pxr::VtDoubleArray knots; + curve_prim_.GetKnotsAttr().Get(&knots, motionSampleTime); + + pxr::VtVec3fArray usdNormals; + curve_prim_.GetNormalsAttr().Get(&usdNormals, motionSampleTime); + + /* If normals, extrude, else bevel. + * Perhaps to be replaced by Blender USD Schema. */ + if (!usdNormals.empty()) { + /* Set extrusion to 1. */ + curve_->ext1 = 1.0f; + } + else { + /* Set bevel depth to 1. */ + curve_->ext2 = 1.0f; + } + + size_t idx = 0; + for (size_t i = 0; i < usdCounts.size(); i++) { + const int num_verts = usdCounts[i]; + + Nurb *nu = static_cast<Nurb *>(MEM_callocN(sizeof(Nurb), __func__)); + nu->flag = CU_SMOOTH; + nu->type = CU_NURBS; + + nu->resolu = cu->resolu; + nu->resolv = cu->resolv; + + nu->pntsu = num_verts; + nu->pntsv = 1; + + if (i < orders.size()) { + nu->orderu = static_cast<short>(orders[i]); + } + else { + nu->orderu = 4; + nu->orderv = 4; + } + + /* TODO(makowalski): investigate setting Cyclic U and Endpoint U options. */ +#if 0 + if (knots.size() > 3) { + if ((knots[0] == knots[1]) && (knots[knots.size()] == knots[knots.size() - 1])) { + nu->flagu |= CU_NURB_ENDPOINT; + } else { + nu->flagu |= CU_NURB_CYCLIC; + } + } +#endif + + float weight = 1.0f; + + nu->bp = static_cast<BPoint *>(MEM_callocN(sizeof(BPoint) * nu->pntsu, __func__)); + BPoint *bp = nu->bp; + + for (int j = 0; j < nu->pntsu; j++, bp++, idx++) { + bp->vec[0] = (float)usdPoints[idx][0]; + bp->vec[1] = (float)usdPoints[idx][1]; + bp->vec[2] = (float)usdPoints[idx][2]; + bp->vec[3] = weight; + bp->f1 = SELECT; + bp->weight = weight; + + float radius = 0.1f; + if (idx < usdWidths.size()) { + radius = usdWidths[idx]; + } + + bp->radius = radius; + } + + if (!set_knots(knots, nu->knotsu)) { + BKE_nurb_knot_calc_u(nu); + } + + BLI_addtail(BKE_curve_nurbs_get(cu), nu); + } +} + +Mesh *USDNurbsReader::read_mesh(struct Mesh * /* existing_mesh */, + const double motionSampleTime, + const int /* read_flag */, + const char ** /* err_str */) +{ + pxr::UsdGeomCurves curve_prim_(prim_); + + pxr::UsdAttribute widthsAttr = curve_prim_.GetWidthsAttr(); + pxr::UsdAttribute vertexAttr = curve_prim_.GetCurveVertexCountsAttr(); + pxr::UsdAttribute pointsAttr = curve_prim_.GetPointsAttr(); + + pxr::VtIntArray usdCounts; + + vertexAttr.Get(&usdCounts, motionSampleTime); + int num_subcurves = usdCounts.size(); + + pxr::VtVec3fArray usdPoints; + pointsAttr.Get(&usdPoints, motionSampleTime); + + int vertex_idx = 0; + int curve_idx; + Curve *curve = static_cast<Curve *>(object_->data); + + const int curve_count = BLI_listbase_count(&curve->nurb); + bool same_topology = curve_count == num_subcurves; + + if (same_topology) { + Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); + for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { + const int num_in_usd = usdCounts[curve_idx]; + const int num_in_blender = nurbs->pntsu; + + if (num_in_usd != num_in_blender) { + same_topology = false; + break; + } + } + } + + if (!same_topology) { + BKE_nurbList_free(&curve->nurb); + read_curve_sample(curve, motionSampleTime); + } + else { + Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first); + for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) { + const int totpoint = usdCounts[curve_idx]; + + if (nurbs->bp) { + BPoint *point = nurbs->bp; + + for (int i = 0; i < totpoint; i++, point++, vertex_idx++) { + point->vec[0] = usdPoints[vertex_idx][0]; + point->vec[1] = usdPoints[vertex_idx][1]; + point->vec[2] = usdPoints[vertex_idx][2]; + } + } + else if (nurbs->bezt) { + BezTriple *bezier = nurbs->bezt; + + for (int i = 0; i < totpoint; i++, bezier++, vertex_idx++) { + bezier->vec[1][0] = usdPoints[vertex_idx][0]; + bezier->vec[1][1] = usdPoints[vertex_idx][1]; + bezier->vec[1][2] = usdPoints[vertex_idx][2]; + } + } + } + } + + return BKE_mesh_new_nomain_from_curve(object_); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_nurbs.h b/source/blender/io/usd/intern/usd_reader_nurbs.h new file mode 100644 index 00000000000..33a4acf503e --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_nurbs.h @@ -0,0 +1,61 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_geom.h" + +#include "pxr/usd/usdGeom/nurbsCurves.h" + +struct Curve; + +namespace blender::io::usd { + +class USDNurbsReader : public USDGeomReader { + protected: + pxr::UsdGeomNurbsCurves curve_prim_; + Curve *curve_; + + public: + USDNurbsReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDGeomReader(prim, import_params, settings), curve_prim_(prim), curve_(nullptr) + { + } + + bool valid() const override + { + return static_cast<bool>(curve_prim_); + } + + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; + + void read_curve_sample(Curve *cu, double motionSampleTime); + + Mesh *read_mesh(struct Mesh *existing_mesh, + double motionSampleTime, + int read_flag, + const char **err_str) override; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_prim.cc b/source/blender/io/usd/intern/usd_reader_prim.cc new file mode 100644 index 00000000000..abd70f49f23 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_prim.cc @@ -0,0 +1,80 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_prim.h" + +#include "BLI_utildefines.h" + +namespace blender::io::usd { + +USDPrimReader::USDPrimReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : name_(prim.GetName().GetString()), + prim_path_(prim.GetPrimPath().GetString()), + object_(nullptr), + prim_(prim), + import_params_(import_params), + parent_reader_(nullptr), + settings_(&settings), + refcount_(0) +{ +} + +USDPrimReader::~USDPrimReader() = default; + +const pxr::UsdPrim &USDPrimReader::prim() const +{ + return prim_; +} + +Object *USDPrimReader::object() const +{ + return object_; +} + +void USDPrimReader::object(Object *ob) +{ + object_ = ob; +} + +bool USDPrimReader::valid() const +{ + return prim_.IsValid(); +} + +int USDPrimReader::refcount() const +{ + return refcount_; +} + +void USDPrimReader::incref() +{ + refcount_++; +} + +void USDPrimReader::decref() +{ + refcount_--; + BLI_assert(refcount_ >= 0); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_prim.h b/source/blender/io/usd/intern/usd_reader_prim.h new file mode 100644 index 00000000000..5aff52f011f --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_prim.h @@ -0,0 +1,131 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" + +#include <pxr/usd/usd/prim.h> + +struct Main; +struct Object; + +namespace blender::io::usd { + +struct ImportSettings { + bool do_convert_mat; + float conversion_mat[4][4]; + + int from_up; + int from_forward; + float scale; + bool is_sequence; + bool set_frame_range; + + /* Length and frame offset of file sequences. */ + int sequence_len; + int sequence_offset; + + /* From MeshSeqCacheModifierData.read_flag */ + int read_flag; + + bool validate_meshes; + + CacheFile *cache_file; + + ImportSettings() + : do_convert_mat(false), + from_up(0), + from_forward(0), + scale(1.0f), + is_sequence(false), + set_frame_range(false), + sequence_len(1), + sequence_offset(0), + read_flag(0), + validate_meshes(false), + cache_file(NULL) + { + } +}; + +/* Most generic USD Reader. */ + +class USDPrimReader { + + protected: + std::string name_; + std::string prim_path_; + Object *object_; + pxr::UsdPrim prim_; + const USDImportParams &import_params_; + USDPrimReader *parent_reader_; + const ImportSettings *settings_; + int refcount_; + + public: + USDPrimReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings); + virtual ~USDPrimReader(); + + const pxr::UsdPrim &prim() const; + + virtual bool valid() const; + + virtual void create_object(Main *bmain, double motionSampleTime) = 0; + virtual void read_object_data(Main * /* bmain */, double /* motionSampleTime */){}; + + Object *object() const; + void object(Object *ob); + + USDPrimReader *parent() const + { + return parent_reader_; + } + void parent(USDPrimReader *parent) + { + parent_reader_ = parent; + } + + /* Since readers might be referenced through handles + * maintained by modifiers and constraints, we provide + * a reference count to facilitate managing the object + * lifetime. + * TODO(makowalski): investigate transitioning to using + * smart pointers for readers, or, alternatively look into + * making the lifetime management more robust, e.g., by + * making the destructors protected and implementing deletion + * in decref(), etc. */ + int refcount() const; + void incref(); + void decref(); + + const std::string &name() const + { + return name_; + } + const std::string &prim_path() const + { + return prim_path_; + } +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc new file mode 100644 index 00000000000..233b3d9da4d --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -0,0 +1,324 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation and + * NVIDIA Corporation. All rights reserved. + */ + +#include "usd_reader_stage.h" +#include "usd_reader_camera.h" +#include "usd_reader_curve.h" +#include "usd_reader_instance.h" +#include "usd_reader_light.h" +#include "usd_reader_mesh.h" +#include "usd_reader_nurbs.h" +#include "usd_reader_prim.h" +#include "usd_reader_volume.h" +#include "usd_reader_xform.h" + +#include <pxr/pxr.h> +#include <pxr/usd/usdGeom/camera.h> +#include <pxr/usd/usdGeom/curves.h> +#include <pxr/usd/usdGeom/mesh.h> +#include <pxr/usd/usdGeom/nurbsCurves.h> +#include <pxr/usd/usdGeom/scope.h> +#include <pxr/usd/usdLux/light.h> + +#include <iostream> + +namespace blender::io::usd { + +USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage, + const USDImportParams ¶ms, + const ImportSettings &settings) + : stage_(stage), params_(params), settings_(settings) +{ +} + +USDStageReader::~USDStageReader() +{ + clear_readers(); +} + +bool USDStageReader::valid() const +{ + return stage_; +} + +USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim) +{ + if (params_.import_cameras && prim.IsA<pxr::UsdGeomCamera>()) { + return new USDCameraReader(prim, params_, settings_); + } + if (params_.import_curves && prim.IsA<pxr::UsdGeomBasisCurves>()) { + return new USDCurvesReader(prim, params_, settings_); + } + if (params_.import_curves && prim.IsA<pxr::UsdGeomNurbsCurves>()) { + return new USDNurbsReader(prim, params_, settings_); + } + if (params_.import_meshes && prim.IsA<pxr::UsdGeomMesh>()) { + return new USDMeshReader(prim, params_, settings_); + } + if (params_.import_lights && prim.IsA<pxr::UsdLuxLight>()) { + return new USDLightReader(prim, params_, settings_); + } + if (params_.import_volumes && prim.IsA<pxr::UsdVolVolume>()) { + return new USDVolumeReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdGeomImageable>()) { + return new USDXformReader(prim, params_, settings_); + } + + return nullptr; +} + +USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim) +{ + if (prim.IsA<pxr::UsdGeomCamera>()) { + return new USDCameraReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdGeomBasisCurves>()) { + return new USDCurvesReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdGeomNurbsCurves>()) { + return new USDNurbsReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdGeomMesh>()) { + return new USDMeshReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdLuxLight>()) { + return new USDLightReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdVolVolume>()) { + return new USDVolumeReader(prim, params_, settings_); + } + if (prim.IsA<pxr::UsdGeomImageable>()) { + return new USDXformReader(prim, params_, settings_); + } + return nullptr; +} + +/* Returns true if the given prim should be included in the + * traversal based on the import options and the prim's visibility + * attribute. Note that the prim will be trivially included + * if it has no visibility attribute or if the visibility + * is inherited. */ +bool USDStageReader::include_by_visibility(const pxr::UsdGeomImageable &imageable) const +{ + if (!params_.import_visible_only) { + /* Invisible prims are allowed. */ + return true; + } + + pxr::UsdAttribute visibility_attr = imageable.GetVisibilityAttr(); + + if (!visibility_attr) { + /* No visibility attribute, so allow. */ + return true; + } + + /* Include if the prim has an animating visibility attribute or is not invisible. */ + + if (visibility_attr.ValueMightBeTimeVarying()) { + return true; + } + + pxr::TfToken visibility; + visibility_attr.Get(&visibility); + return visibility != pxr::UsdGeomTokens->invisible; +} + +/* Returns true if the given prim should be included in the + * traversal based on the import options and the prim's purpose + * attribute. E.g., return false (to exclude the prim) if the prim + * represents guide geometry and the 'Import Guide' option is + * toggled off. */ +bool USDStageReader::include_by_purpose(const pxr::UsdGeomImageable &imageable) const +{ + if (params_.import_guide && params_.import_proxy && params_.import_render) { + /* The options allow any purpose, so we trivially include the prim. */ + return true; + } + + pxr::UsdAttribute purpose_attr = imageable.GetPurposeAttr(); + + if (!purpose_attr) { + /* No purpose attribute, so trivially include the prim. */ + return true; + } + + pxr::TfToken purpose; + purpose_attr.Get(&purpose); + + if (purpose == pxr::UsdGeomTokens->guide) { + return params_.import_guide; + } + if (purpose == pxr::UsdGeomTokens->proxy) { + return params_.import_proxy; + } + if (purpose == pxr::UsdGeomTokens->render) { + return params_.import_render; + } + + return true; +} + +/* Determine if the given reader can use the parent of the encapsulated USD prim + * to compute the Blender object's transform. If so, the reader is appropriately + * flagged and the function returns true. Otherwise, the function returns false. */ +static bool merge_with_parent(USDPrimReader *reader) +{ + USDXformReader *xform_reader = dynamic_cast<USDXformReader *>(reader); + + if (!xform_reader) { + return false; + } + + /* Check if the Xform reader is already merged. */ + if (xform_reader->use_parent_xform()) { + return false; + } + + /* Only merge if the parent is an Xform. */ + if (!xform_reader->prim().GetParent().IsA<pxr::UsdGeomXform>()) { + return false; + } + + /* Don't merge Xform and Scope prims. */ + if (xform_reader->prim().IsA<pxr::UsdGeomXform>() || + xform_reader->prim().IsA<pxr::UsdGeomScope>()) { + return false; + } + + /* Don't merge if the prim has authored transform ops. */ + if (xform_reader->prim_has_xform_ops()) { + return false; + } + + /* Flag the Xform reader as merged. */ + xform_reader->set_use_parent_xform(true); + + return true; +} + +USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &prim) +{ + if (prim.IsA<pxr::UsdGeomImageable>()) { + pxr::UsdGeomImageable imageable(prim); + + if (!include_by_purpose(imageable)) { + return nullptr; + } + + if (!include_by_visibility(imageable)) { + return nullptr; + } + } + + pxr::Usd_PrimFlagsPredicate filter_predicate = pxr::UsdPrimDefaultPredicate; + + if (params_.import_instance_proxies) { + filter_predicate = pxr::UsdTraverseInstanceProxies(filter_predicate); + } + + pxr::UsdPrimSiblingRange children = prim.GetFilteredChildren(filter_predicate); + + std::vector<USDPrimReader *> child_readers; + + for (const auto &childPrim : children) { + if (USDPrimReader *child_reader = collect_readers(bmain, childPrim)) { + child_readers.push_back(child_reader); + } + } + + if (prim.IsPseudoRoot()) { + return nullptr; + } + + /* Check if we can merge an Xform with its child prim. */ + if (child_readers.size() == 1) { + + USDPrimReader *child_reader = child_readers.front(); + + if (merge_with_parent(child_reader)) { + return child_reader; + } + } + + USDPrimReader *reader = create_reader_if_allowed(prim); + + if (!reader) { + return nullptr; + } + + reader->create_object(bmain, 0.0); + + readers_.push_back(reader); + reader->incref(); + + /* Set each child reader's parent. */ + for (USDPrimReader *child_reader : child_readers) { + child_reader->parent(reader); + } + + return reader; +} + +void USDStageReader::collect_readers(Main *bmain) +{ + if (!valid()) { + return; + } + + clear_readers(); + + /* Iterate through the stage. */ + pxr::UsdPrim root = stage_->GetPseudoRoot(); + + std::string prim_path_mask(params_.prim_path_mask); + + if (!prim_path_mask.empty()) { + pxr::UsdPrim prim = stage_->GetPrimAtPath(pxr::SdfPath(prim_path_mask)); + if (prim.IsValid()) { + root = prim; + } + else { + std::cerr << "WARNING: Prim Path Mask " << prim_path_mask + << " does not specify a valid prim.\n"; + } + } + + stage_->SetInterpolationType(pxr::UsdInterpolationType::UsdInterpolationTypeHeld); + collect_readers(bmain, root); +} + +void USDStageReader::clear_readers() +{ + for (USDPrimReader *reader : readers_) { + if (!reader) { + continue; + } + + reader->decref(); + + if (reader->refcount() == 0) { + delete reader; + } + } + + readers_.clear(); +} + +} // Namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_stage.h b/source/blender/io/usd/intern/usd_reader_stage.h new file mode 100644 index 00000000000..ba223962c0c --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_stage.h @@ -0,0 +1,90 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation and + * NVIDIA Corporation. All rights reserved. + */ +#pragma once + +struct Main; + +#include "usd.h" +#include "usd_reader_prim.h" + +#include <pxr/usd/usd/stage.h> +#include <pxr/usd/usdGeom/imageable.h> + +#include <vector> + +struct ImportSettings; + +namespace blender::io::usd { + +typedef std::map<pxr::SdfPath, std::vector<USDPrimReader *>> ProtoReaderMap; + +class USDStageReader { + + protected: + pxr::UsdStageRefPtr stage_; + USDImportParams params_; + ImportSettings settings_; + + std::vector<USDPrimReader *> readers_; + + public: + USDStageReader(pxr::UsdStageRefPtr stage, + const USDImportParams ¶ms, + const ImportSettings &settings); + + ~USDStageReader(); + + USDPrimReader *create_reader_if_allowed(const pxr::UsdPrim &prim); + + USDPrimReader *create_reader(const pxr::UsdPrim &prim); + + void collect_readers(struct Main *bmain); + + bool valid() const; + + pxr::UsdStageRefPtr stage() + { + return stage_; + } + const USDImportParams ¶ms() const + { + return params_; + } + + const ImportSettings &settings() const + { + return settings_; + } + + void clear_readers(); + + const std::vector<USDPrimReader *> &readers() const + { + return readers_; + }; + + private: + USDPrimReader *collect_readers(Main *bmain, const pxr::UsdPrim &prim); + + bool include_by_visibility(const pxr::UsdGeomImageable &imageable) const; + + bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const; +}; + +}; // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_volume.cc b/source/blender/io/usd/intern/usd_reader_volume.cc new file mode 100644 index 00000000000..871f791c1dd --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_volume.cc @@ -0,0 +1,114 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_volume.h" + +#include "BKE_object.h" +#include "BKE_volume.h" + +#include "DNA_object_types.h" +#include "DNA_volume_types.h" + +#include <pxr/usd/usdVol/openVDBAsset.h> +#include <pxr/usd/usdVol/volume.h> + +#include <iostream> + +namespace usdtokens { + +static const pxr::TfToken density("density", pxr::TfToken::Immortal); + +} + +namespace blender::io::usd { + +void USDVolumeReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + Volume *volume = (Volume *)BKE_volume_add(bmain, name_.c_str()); + + object_ = BKE_object_add_only_object(bmain, OB_VOLUME, name_.c_str()); + object_->data = volume; +} + +void USDVolumeReader::read_object_data(Main *bmain, const double motionSampleTime) +{ + if (!volume_) { + return; + } + + Volume *volume = static_cast<Volume *>(object_->data); + + if (!volume) { + return; + } + + pxr::UsdVolVolume::FieldMap fields = volume_.GetFieldPaths(); + + for (pxr::UsdVolVolume::FieldMap::const_iterator it = fields.begin(); it != fields.end(); ++it) { + + pxr::UsdPrim fieldPrim = prim_.GetStage()->GetPrimAtPath(it->second); + + if (!fieldPrim.IsA<pxr::UsdVolOpenVDBAsset>()) { + continue; + } + + pxr::UsdVolOpenVDBAsset fieldBase(fieldPrim); + + pxr::UsdAttribute fieldNameAttr = fieldBase.GetFieldNameAttr(); + + if (fieldNameAttr.IsAuthored()) { + pxr::TfToken fieldName; + fieldNameAttr.Get(&fieldName, motionSampleTime); + + /* A Blender volume creates density by default. */ + if (fieldName != usdtokens::density) { + BKE_volume_grid_add(volume, fieldName.GetString().c_str(), VOLUME_GRID_FLOAT); + } + } + + pxr::UsdAttribute filepathAttr = fieldBase.GetFilePathAttr(); + + if (filepathAttr.IsAuthored()) { + pxr::SdfAssetPath fp; + filepathAttr.Get(&fp, motionSampleTime); + + if (filepathAttr.ValueMightBeTimeVarying()) { + std::vector<double> filePathTimes; + filepathAttr.GetTimeSamples(&filePathTimes); + + if (!filePathTimes.empty()) { + int start = static_cast<int>(filePathTimes.front()); + int end = static_cast<int>(filePathTimes.back()); + + volume->is_sequence = static_cast<char>(true); + volume->frame_start = start; + volume->frame_duration = (end - start) + 1; + } + } + + std::string filepath = fp.GetResolvedPath(); + + strcpy(volume->filepath, filepath.c_str()); + } + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_volume.h b/source/blender/io/usd/intern/usd_reader_volume.h new file mode 100644 index 00000000000..ca2fddb5531 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_volume.h @@ -0,0 +1,49 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_xform.h" + +#include "pxr/usd/usdVol/volume.h" + +namespace blender::io::usd { + +class USDVolumeReader : public USDXformReader { + private: + pxr::UsdVolVolume volume_; + + public: + USDVolumeReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDXformReader(prim, import_params, settings), volume_(prim) + { + } + + bool valid() const override + { + return static_cast<bool>(volume_); + } + + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_xform.cc b/source/blender/io/usd/intern/usd_reader_xform.cc new file mode 100644 index 00000000000..eebcc5eb3d5 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_xform.cc @@ -0,0 +1,184 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ + +#include "usd_reader_xform.h" + +#include "BKE_constraint.h" +#include "BKE_lib_id.h" +#include "BKE_library.h" +#include "BKE_modifier.h" +#include "BKE_object.h" + +#include "BLI_math_geom.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "DNA_cachefile_types.h" +#include "DNA_constraint_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_space_types.h" /* for FILE_MAX */ + +#include <pxr/base/gf/math.h> +#include <pxr/base/gf/matrix4f.h> + +#include <pxr/usd/usdGeom/xform.h> + +namespace blender::io::usd { + +void USDXformReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + object_ = BKE_object_add_only_object(bmain, OB_EMPTY, name_.c_str()); + object_->empty_drawsize = 0.1f; + object_->data = nullptr; +} + +void USDXformReader::read_object_data(Main * /* bmain */, const double motionSampleTime) +{ + bool is_constant; + float transform_from_usd[4][4]; + + read_matrix(transform_from_usd, motionSampleTime, import_params_.scale, &is_constant); + + if (!is_constant) { + bConstraint *con = BKE_constraint_add_for_object( + object_, nullptr, CONSTRAINT_TYPE_TRANSFORM_CACHE); + bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data); + + std::string prim_path = use_parent_xform_ ? prim_.GetParent().GetPath().GetAsString() : + prim_path_; + + BLI_strncpy(data->object_path, prim_path.c_str(), FILE_MAX); + + data->cache_file = settings_->cache_file; + id_us_plus(&data->cache_file->id); + } + + BKE_object_apply_mat4(object_, transform_from_usd, true, false); +} + +void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */, + const float time, + const float scale, + bool *r_is_constant) +{ + if (r_is_constant) { + *r_is_constant = true; + } + + unit_m4(r_mat); + + pxr::UsdGeomXformable xformable; + + if (use_parent_xform_) { + xformable = pxr::UsdGeomXformable(prim_.GetParent()); + } + else { + xformable = pxr::UsdGeomXformable(prim_); + } + + if (!xformable) { + /* This might happen if the prim is a Scope. */ + return; + } + + if (r_is_constant) { + *r_is_constant = !xformable.TransformMightBeTimeVarying(); + } + + pxr::GfMatrix4d usd_local_xf; + bool reset_xform_stack; + xformable.GetLocalTransformation(&usd_local_xf, &reset_xform_stack, time); + + /* Convert the result to a float matrix. */ + pxr::GfMatrix4f mat4f = pxr::GfMatrix4f(usd_local_xf); + mat4f.Get(r_mat); + + /* Apply global scaling and rotation only to root objects, parenting + * will propagate it. */ + if ((scale != 1.0 || settings_->do_convert_mat) && is_root_xform_) { + + if (scale != 1.0f) { + float scale_mat[4][4]; + scale_m4_fl(scale_mat, scale); + mul_m4_m4m4(r_mat, scale_mat, r_mat); + } + + if (settings_->do_convert_mat) { + mul_m4_m4m4(r_mat, settings_->conversion_mat, r_mat); + } + } +} + +bool USDXformReader::prim_has_xform_ops() const +{ + pxr::UsdGeomXformable xformable(prim_); + + if (!xformable) { + /* This might happen if the prim is a Scope. */ + return false; + } + + bool reset_xform_stack = false; + + return !xformable.GetOrderedXformOps(&reset_xform_stack).empty(); +} + +bool USDXformReader::is_root_xform_prim() const +{ + if (!prim_.IsValid()) { + return false; + } + + if (prim_.IsInMaster()) { + /* We don't consider prototypes to be root prims, + * because we never want to apply global scaling + * or rotations to the prototypes themselves. */ + return false; + } + + if (prim_.IsA<pxr::UsdGeomXformable>()) { + /* If this prim doesn't have an ancestor that's a + * UsdGeomXformable, then it's a root prim. Note + * that it's not sufficient to only check the immediate + * parent prim, since the immediate parent could be a + * UsdGeomScope that has an xformable ancestor. */ + pxr::UsdPrim cur_parent = prim_.GetParent(); + + if (use_parent_xform_) { + cur_parent = cur_parent.GetParent(); + } + + while (cur_parent && !cur_parent.IsPseudoRoot()) { + if (cur_parent.IsA<pxr::UsdGeomXformable>()) { + return false; + } + cur_parent = cur_parent.GetParent(); + } + + /* We didn't find an xformable ancestor. */ + return true; + } + + return false; +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_xform.h b/source/blender/io/usd/intern/usd_reader_xform.h new file mode 100644 index 00000000000..587ac373a4f --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_xform.h @@ -0,0 +1,68 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Adapted from the Blender Alembic importer implementation. + * + * Modifications Copyright (C) 2021 Tangent Animation. + * All rights reserved. + */ +#pragma once + +#include "usd.h" +#include "usd_reader_prim.h" + +namespace blender::io::usd { + +class USDXformReader : public USDPrimReader { + private: + bool use_parent_xform_; + + /* Indicates if the created object is the root of a + * transform hierarchy. */ + bool is_root_xform_; + + public: + USDXformReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDPrimReader(prim, import_params, settings), + use_parent_xform_(false), + is_root_xform_(is_root_xform_prim()) + { + } + + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; + + void read_matrix(float r_mat[4][4], const float time, const float scale, bool *r_is_constant); + + bool use_parent_xform() const + { + return use_parent_xform_; + } + void set_use_parent_xform(bool flag) + { + use_parent_xform_ = flag; + is_root_xform_ = is_root_xform_prim(); + } + + bool prim_has_xform_ops() const; + + protected: + /* Returns true if the contained USD prim is the root of a transform hierarchy. */ + bool is_root_xform_prim() const; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h index 40e2d0d8674..6b6b2d37162 100644 --- a/source/blender/io/usd/usd.h +++ b/source/blender/io/usd/usd.h @@ -26,6 +26,10 @@ extern "C" { #endif struct bContext; +struct Object; +struct CacheArchiveHandle; +struct CacheReader; +struct CacheFile; struct USDExportParams { bool export_animation; @@ -39,6 +43,34 @@ struct USDExportParams { enum eEvaluationMode evaluation_mode; }; +struct USDImportParams { + float scale; + bool is_sequence; + bool set_frame_range; + int sequence_len; + int offset; + bool validate_meshes; + char mesh_read_flag; + bool import_cameras; + bool import_curves; + bool import_lights; + bool import_materials; + bool import_meshes; + bool import_volumes; + char *prim_path_mask; + bool import_subdiv; + bool import_instance_proxies; + bool create_collection; + bool import_guide; + bool import_proxy; + bool import_render; + bool import_visible_only; + bool use_instancing; + bool import_usd_preview; + bool set_material_blend; + float light_intensity_scale; +}; + /* The USD_export takes a as_background_job parameter, and returns a boolean. * * When as_background_job=true, returns false immediately after scheduling @@ -53,8 +85,45 @@ bool USD_export(struct bContext *C, const struct USDExportParams *params, bool as_background_job); +bool USD_import(struct bContext *C, + const char *filepath, + const struct USDImportParams *params, + bool as_background_job); + int USD_get_version(void); +/* USD Import and Mesh Cache interface. */ + +struct CacheArchiveHandle *USD_create_handle(struct Main *bmain, + const char *filename, + struct ListBase *object_paths); + +void USD_free_handle(struct CacheArchiveHandle *handle); + +void USD_get_transform(struct CacheReader *reader, float r_mat[4][4], float time, float scale); + +/* Either modifies current_mesh in-place or constructs a new mesh. */ +struct Mesh *USD_read_mesh(struct CacheReader *reader, + struct Object *ob, + struct Mesh *existing_mesh, + const float time, + const char **err_str, + int read_flag); + +bool USD_mesh_topology_changed(struct CacheReader *reader, + struct Object *ob, + struct Mesh *existing_mesh, + const float time, + const char **err_str); + +struct CacheReader *CacheReader_open_usd_object(struct CacheArchiveHandle *handle, + struct CacheReader *reader, + struct Object *object, + const char *object_path); + +void USD_CacheReader_incref(struct CacheReader *reader); +void USD_CacheReader_free(struct CacheReader *reader); + #ifdef __cplusplus } #endif |