This patch moves material indices from the mesh `MPoly` struct to a generic integer attribute. The builtin material index was already exposed in geometry nodes, but this makes it a "proper" attribute accessible with Python and visible in the "Attributes" panel. The goals of the refactor are code simplification and memory and performance improvements, mainly because the attribute doesn't have to be stored and processed if there are no materials. However, until 4.0, material indices will still be read and written in the old format, meaning there may be a temporary increase in memory usage. Further notes: * Completely removing the `MPoly.mat_nr` after 4.0 may require changes to DNA or introducing a new `MPoly` type. * Geometry nodes regression tests didn't look at material indices, so the change reveals a bug in the realize instances node that I fixed. * Access to material indices from the RNA `MeshPolygon` type is slower with this patch. The `material_index` attribute can be used instead. * Cycles is changed to read from the attribute instead. * BMesh isn't changed in this patch. Theoretically it could be though, to save 2 bytes per face when less than two materials are used. * Eventually we could use a 16 bit integer attribute type instead. Ref T95967 Differential Revision: https://developer.blender.org/D15675
863 lines
30 KiB
C++
863 lines
30 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup freestyle
|
|
*/
|
|
|
|
#include "BlenderStrokeRenderer.h"
|
|
|
|
#include "../application/AppConfig.h"
|
|
#include "../stroke/Canvas.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_prototypes.h"
|
|
#include "RNA_types.h"
|
|
|
|
#include "DNA_camera_types.h"
|
|
#include "DNA_collection_types.h"
|
|
#include "DNA_linestyle_types.h"
|
|
#include "DNA_listBase.h"
|
|
#include "DNA_material_types.h"
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_screen_types.h"
|
|
|
|
#include "BKE_collection.h"
|
|
#include "BKE_customdata.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_idprop.h"
|
|
#include "BKE_layer.h"
|
|
#include "BKE_lib_id.h" /* free_libblock */
|
|
#include "BKE_main.h"
|
|
#include "BKE_material.h"
|
|
#include "BKE_mesh.h"
|
|
#include "BKE_node.h"
|
|
#include "BKE_node_tree_update.h"
|
|
#include "BKE_object.h"
|
|
#include "BKE_scene.h"
|
|
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math_color.h"
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
#include "DEG_depsgraph_build.h"
|
|
|
|
#include "RE_pipeline.h"
|
|
|
|
#include "render_types.h"
|
|
|
|
#include <climits>
|
|
|
|
namespace Freestyle {
|
|
|
|
const char *BlenderStrokeRenderer::uvNames[] = {"along_stroke", "along_stroke_tips"};
|
|
|
|
BlenderStrokeRenderer::BlenderStrokeRenderer(Render *re, int render_count)
|
|
{
|
|
freestyle_bmain = BKE_main_new();
|
|
|
|
/* We use the same window manager for freestyle bmain as
|
|
* real bmain uses. This is needed because freestyle's
|
|
* bmain could be used to tag scenes for update, which
|
|
* implies call of ED_render_scene_update in some cases
|
|
* and that function requires proper window manager
|
|
* to present (sergey)
|
|
*/
|
|
freestyle_bmain->wm = re->main->wm;
|
|
|
|
// for stroke mesh generation
|
|
_width = re->winx;
|
|
_height = re->winy;
|
|
|
|
old_scene = re->scene;
|
|
|
|
char name[MAX_ID_NAME - 2];
|
|
BLI_snprintf(name, sizeof(name), "FRS%d_%s", render_count, re->scene->id.name + 2);
|
|
freestyle_scene = BKE_scene_add(freestyle_bmain, name);
|
|
freestyle_scene->r.cfra = old_scene->r.cfra;
|
|
freestyle_scene->r.mode = old_scene->r.mode & ~(R_EDGE_FRS | R_BORDER);
|
|
freestyle_scene->r.xsch = re->rectx; // old_scene->r.xsch
|
|
freestyle_scene->r.ysch = re->recty; // old_scene->r.ysch
|
|
freestyle_scene->r.xasp = 1.0f; // old_scene->r.xasp;
|
|
freestyle_scene->r.yasp = 1.0f; // old_scene->r.yasp;
|
|
freestyle_scene->r.size = 100; // old_scene->r.size
|
|
freestyle_scene->r.color_mgt_flag = 0; // old_scene->r.color_mgt_flag;
|
|
freestyle_scene->r.scemode = (old_scene->r.scemode &
|
|
~(R_SINGLE_LAYER | R_NO_FRAME_UPDATE | R_MULTIVIEW)) &
|
|
(re->r.scemode);
|
|
freestyle_scene->r.flag = old_scene->r.flag;
|
|
freestyle_scene->r.threads = old_scene->r.threads;
|
|
freestyle_scene->r.border.xmin = old_scene->r.border.xmin;
|
|
freestyle_scene->r.border.ymin = old_scene->r.border.ymin;
|
|
freestyle_scene->r.border.xmax = old_scene->r.border.xmax;
|
|
freestyle_scene->r.border.ymax = old_scene->r.border.ymax;
|
|
strcpy(freestyle_scene->r.pic, old_scene->r.pic);
|
|
freestyle_scene->r.dither_intensity = old_scene->r.dither_intensity;
|
|
STRNCPY(freestyle_scene->r.engine, old_scene->r.engine);
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
cout << "Stroke rendering engine : " << freestyle_scene->r.engine << endl;
|
|
}
|
|
freestyle_scene->r.im_format.planes = R_IMF_PLANES_RGBA;
|
|
freestyle_scene->r.im_format.imtype = R_IMF_IMTYPE_PNG;
|
|
|
|
// Copy ID properties, including Cycles render properties
|
|
if (old_scene->id.properties) {
|
|
freestyle_scene->id.properties = IDP_CopyProperty_ex(old_scene->id.properties, 0);
|
|
}
|
|
// Copy eevee render settings.
|
|
BKE_scene_copy_data_eevee(freestyle_scene, old_scene);
|
|
|
|
/* Render with transparent background. */
|
|
freestyle_scene->r.alphamode = R_ALPHAPREMUL;
|
|
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
printf("%s: %d thread(s)\n", __func__, BKE_render_num_threads(&freestyle_scene->r));
|
|
}
|
|
|
|
BKE_scene_set_background(freestyle_bmain, freestyle_scene);
|
|
|
|
// Scene layer.
|
|
ViewLayer *view_layer = (ViewLayer *)freestyle_scene->view_layers.first;
|
|
view_layer->layflag = SCE_LAY_SOLID;
|
|
|
|
// Camera
|
|
Object *object_camera = BKE_object_add(freestyle_bmain, view_layer, OB_CAMERA, nullptr);
|
|
|
|
Camera *camera = (Camera *)object_camera->data;
|
|
camera->type = CAM_ORTHO;
|
|
camera->ortho_scale = max(re->rectx, re->recty);
|
|
camera->clip_start = 0.1f;
|
|
camera->clip_end = 100.0f;
|
|
|
|
_z_delta = 0.00001f;
|
|
_z = camera->clip_start + _z_delta;
|
|
|
|
object_camera->loc[0] = re->disprect.xmin + 0.5f * re->rectx;
|
|
object_camera->loc[1] = re->disprect.ymin + 0.5f * re->recty;
|
|
object_camera->loc[2] = 1.0f;
|
|
|
|
freestyle_scene->camera = object_camera;
|
|
|
|
// Reset serial mesh ID (used for BlenderStrokeRenderer::NewMesh())
|
|
_mesh_id = 0xffffffff;
|
|
|
|
// Create a bNodeTree-to-Material hash table
|
|
_nodetree_hash = BLI_ghash_ptr_new("BlenderStrokeRenderer::_nodetree_hash");
|
|
|
|
// Depsgraph
|
|
freestyle_depsgraph = DEG_graph_new(
|
|
freestyle_bmain, freestyle_scene, view_layer, DAG_EVAL_RENDER);
|
|
DEG_graph_id_tag_update(freestyle_bmain, freestyle_depsgraph, &freestyle_scene->id, 0);
|
|
DEG_graph_id_tag_update(freestyle_bmain, freestyle_depsgraph, &object_camera->id, 0);
|
|
DEG_graph_tag_relations_update(freestyle_depsgraph);
|
|
}
|
|
|
|
BlenderStrokeRenderer::~BlenderStrokeRenderer()
|
|
{
|
|
BLI_ghash_free(_nodetree_hash, nullptr, nullptr);
|
|
|
|
DEG_graph_free(freestyle_depsgraph);
|
|
|
|
FreeStrokeGroups();
|
|
|
|
/* detach the window manager from freestyle bmain (see comments
|
|
* in add_freestyle() for more detail)
|
|
*/
|
|
BLI_listbase_clear(&freestyle_bmain->wm);
|
|
|
|
BKE_main_free(freestyle_bmain);
|
|
}
|
|
|
|
float BlenderStrokeRenderer::get_stroke_vertex_z() const
|
|
{
|
|
float z = _z;
|
|
BlenderStrokeRenderer *self = const_cast<BlenderStrokeRenderer *>(this);
|
|
if (!(_z < _z_delta * 100000.0f)) {
|
|
self->_z_delta *= 10.0f;
|
|
}
|
|
self->_z += _z_delta;
|
|
return -z;
|
|
}
|
|
|
|
unsigned int BlenderStrokeRenderer::get_stroke_mesh_id() const
|
|
{
|
|
unsigned mesh_id = _mesh_id;
|
|
BlenderStrokeRenderer *self = const_cast<BlenderStrokeRenderer *>(this);
|
|
self->_mesh_id--;
|
|
return mesh_id;
|
|
}
|
|
|
|
Material *BlenderStrokeRenderer::GetStrokeShader(Main *bmain,
|
|
bNodeTree *iNodeTree,
|
|
bool do_id_user)
|
|
{
|
|
Material *ma = BKE_material_add(bmain, "stroke_shader");
|
|
bNodeTree *ntree;
|
|
bNode *output_linestyle = nullptr;
|
|
bNodeSocket *fromsock, *tosock;
|
|
PointerRNA fromptr, toptr;
|
|
NodeShaderAttribute *storage;
|
|
|
|
id_us_min(&ma->id);
|
|
|
|
if (iNodeTree) {
|
|
// make a copy of linestyle->nodetree
|
|
ntree = ntreeCopyTree_ex(iNodeTree, bmain, do_id_user);
|
|
|
|
// find the active Output Line Style node
|
|
for (bNode *node = (bNode *)ntree->nodes.first; node; node = node->next) {
|
|
if (node->type == SH_NODE_OUTPUT_LINESTYLE && (node->flag & NODE_DO_OUTPUT)) {
|
|
output_linestyle = node;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
ntree = ntreeAddTree(nullptr, "stroke_shader", "ShaderNodeTree");
|
|
}
|
|
ma->nodetree = ntree;
|
|
ma->use_nodes = 1;
|
|
ma->blend_method = MA_BM_HASHED;
|
|
|
|
bNode *input_attr_color = nodeAddStaticNode(nullptr, ntree, SH_NODE_ATTRIBUTE);
|
|
input_attr_color->locx = 0.0f;
|
|
input_attr_color->locy = -200.0f;
|
|
storage = (NodeShaderAttribute *)input_attr_color->storage;
|
|
BLI_strncpy(storage->name, "Color", sizeof(storage->name));
|
|
|
|
bNode *mix_rgb_color = nodeAddStaticNode(nullptr, ntree, SH_NODE_MIX_RGB_LEGACY);
|
|
mix_rgb_color->custom1 = MA_RAMP_BLEND; // Mix
|
|
mix_rgb_color->locx = 200.0f;
|
|
mix_rgb_color->locy = -200.0f;
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_rgb_color->inputs, 0); // Fac
|
|
RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, tosock, &toptr);
|
|
RNA_float_set(&toptr, "default_value", 0.0f);
|
|
|
|
bNode *input_attr_alpha = nodeAddStaticNode(nullptr, ntree, SH_NODE_ATTRIBUTE);
|
|
input_attr_alpha->locx = 400.0f;
|
|
input_attr_alpha->locy = 300.0f;
|
|
storage = (NodeShaderAttribute *)input_attr_alpha->storage;
|
|
BLI_strncpy(storage->name, "Alpha", sizeof(storage->name));
|
|
|
|
bNode *mix_rgb_alpha = nodeAddStaticNode(nullptr, ntree, SH_NODE_MIX_RGB_LEGACY);
|
|
mix_rgb_alpha->custom1 = MA_RAMP_BLEND; // Mix
|
|
mix_rgb_alpha->locx = 600.0f;
|
|
mix_rgb_alpha->locy = 300.0f;
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_rgb_alpha->inputs, 0); // Fac
|
|
RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, tosock, &toptr);
|
|
RNA_float_set(&toptr, "default_value", 0.0f);
|
|
|
|
bNode *shader_emission = nodeAddStaticNode(nullptr, ntree, SH_NODE_EMISSION);
|
|
shader_emission->locx = 400.0f;
|
|
shader_emission->locy = -200.0f;
|
|
|
|
bNode *input_light_path = nodeAddStaticNode(nullptr, ntree, SH_NODE_LIGHT_PATH);
|
|
input_light_path->locx = 400.0f;
|
|
input_light_path->locy = 100.0f;
|
|
|
|
bNode *mix_shader_color = nodeAddStaticNode(nullptr, ntree, SH_NODE_MIX_SHADER);
|
|
mix_shader_color->locx = 600.0f;
|
|
mix_shader_color->locy = -100.0f;
|
|
|
|
bNode *shader_transparent = nodeAddStaticNode(nullptr, ntree, SH_NODE_BSDF_TRANSPARENT);
|
|
shader_transparent->locx = 600.0f;
|
|
shader_transparent->locy = 100.0f;
|
|
|
|
bNode *mix_shader_alpha = nodeAddStaticNode(nullptr, ntree, SH_NODE_MIX_SHADER);
|
|
mix_shader_alpha->locx = 800.0f;
|
|
mix_shader_alpha->locy = 100.0f;
|
|
|
|
bNode *output_material = nodeAddStaticNode(nullptr, ntree, SH_NODE_OUTPUT_MATERIAL);
|
|
output_material->locx = 1000.0f;
|
|
output_material->locy = 100.0f;
|
|
|
|
fromsock = (bNodeSocket *)BLI_findlink(&input_attr_color->outputs, 0); // Color
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_rgb_color->inputs, 1); // Color1
|
|
nodeAddLink(ntree, input_attr_color, fromsock, mix_rgb_color, tosock);
|
|
|
|
fromsock = (bNodeSocket *)BLI_findlink(&mix_rgb_color->outputs, 0); // Color
|
|
tosock = (bNodeSocket *)BLI_findlink(&shader_emission->inputs, 0); // Color
|
|
nodeAddLink(ntree, mix_rgb_color, fromsock, shader_emission, tosock);
|
|
|
|
fromsock = (bNodeSocket *)BLI_findlink(&shader_emission->outputs, 0); // Emission
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_shader_color->inputs, 2); // Shader (second)
|
|
nodeAddLink(ntree, shader_emission, fromsock, mix_shader_color, tosock);
|
|
|
|
fromsock = (bNodeSocket *)BLI_findlink(&input_light_path->outputs, 0); // In Camera Ray
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_shader_color->inputs, 0); // Fac
|
|
nodeAddLink(ntree, input_light_path, fromsock, mix_shader_color, tosock);
|
|
|
|
fromsock = (bNodeSocket *)BLI_findlink(&mix_rgb_alpha->outputs, 0); // Color
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_shader_alpha->inputs, 0); // Fac
|
|
nodeAddLink(ntree, mix_rgb_alpha, fromsock, mix_shader_alpha, tosock);
|
|
|
|
fromsock = (bNodeSocket *)BLI_findlink(&input_attr_alpha->outputs, 0); // Color
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_rgb_alpha->inputs, 1); // Color1
|
|
nodeAddLink(ntree, input_attr_alpha, fromsock, mix_rgb_alpha, tosock);
|
|
|
|
fromsock = (bNodeSocket *)BLI_findlink(&shader_transparent->outputs, 0); // BSDF
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_shader_alpha->inputs, 1); // Shader (first)
|
|
nodeAddLink(ntree, shader_transparent, fromsock, mix_shader_alpha, tosock);
|
|
|
|
fromsock = (bNodeSocket *)BLI_findlink(&mix_shader_color->outputs, 0); // Shader
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_shader_alpha->inputs, 2); // Shader (second)
|
|
nodeAddLink(ntree, mix_shader_color, fromsock, mix_shader_alpha, tosock);
|
|
|
|
fromsock = (bNodeSocket *)BLI_findlink(&mix_shader_alpha->outputs, 0); // Shader
|
|
tosock = (bNodeSocket *)BLI_findlink(&output_material->inputs, 0); // Surface
|
|
nodeAddLink(ntree, mix_shader_alpha, fromsock, output_material, tosock);
|
|
|
|
if (output_linestyle) {
|
|
bNodeSocket *outsock;
|
|
bNodeLink *link;
|
|
|
|
mix_rgb_color->custom1 = output_linestyle->custom1; // blend_type
|
|
mix_rgb_color->custom2 = output_linestyle->custom2; // use_clamp
|
|
|
|
outsock = (bNodeSocket *)BLI_findlink(&output_linestyle->inputs, 0); // Color
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_rgb_color->inputs, 2); // Color2
|
|
link = (bNodeLink *)BLI_findptr(&ntree->links, outsock, offsetof(bNodeLink, tosock));
|
|
if (link) {
|
|
nodeAddLink(ntree, link->fromnode, link->fromsock, mix_rgb_color, tosock);
|
|
}
|
|
else {
|
|
float color[4];
|
|
RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, outsock, &fromptr);
|
|
RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, tosock, &toptr);
|
|
RNA_float_get_array(&fromptr, "default_value", color);
|
|
RNA_float_set_array(&toptr, "default_value", color);
|
|
}
|
|
|
|
outsock = (bNodeSocket *)BLI_findlink(&output_linestyle->inputs, 1); // Color Fac
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_rgb_color->inputs, 0); // Fac
|
|
link = (bNodeLink *)BLI_findptr(&ntree->links, outsock, offsetof(bNodeLink, tosock));
|
|
if (link) {
|
|
nodeAddLink(ntree, link->fromnode, link->fromsock, mix_rgb_color, tosock);
|
|
}
|
|
else {
|
|
RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, outsock, &fromptr);
|
|
RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, tosock, &toptr);
|
|
RNA_float_set(&toptr, "default_value", RNA_float_get(&fromptr, "default_value"));
|
|
}
|
|
|
|
outsock = (bNodeSocket *)BLI_findlink(&output_linestyle->inputs, 2); // Alpha
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_rgb_alpha->inputs, 2); // Color2
|
|
link = (bNodeLink *)BLI_findptr(&ntree->links, outsock, offsetof(bNodeLink, tosock));
|
|
if (link) {
|
|
nodeAddLink(ntree, link->fromnode, link->fromsock, mix_rgb_alpha, tosock);
|
|
}
|
|
else {
|
|
float color[4];
|
|
RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, outsock, &fromptr);
|
|
RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, tosock, &toptr);
|
|
color[0] = color[1] = color[2] = RNA_float_get(&fromptr, "default_value");
|
|
color[3] = 1.0f;
|
|
RNA_float_set_array(&toptr, "default_value", color);
|
|
}
|
|
|
|
outsock = (bNodeSocket *)BLI_findlink(&output_linestyle->inputs, 3); // Alpha Fac
|
|
tosock = (bNodeSocket *)BLI_findlink(&mix_rgb_alpha->inputs, 0); // Fac
|
|
link = (bNodeLink *)BLI_findptr(&ntree->links, outsock, offsetof(bNodeLink, tosock));
|
|
if (link) {
|
|
nodeAddLink(ntree, link->fromnode, link->fromsock, mix_rgb_alpha, tosock);
|
|
}
|
|
else {
|
|
RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, outsock, &fromptr);
|
|
RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, tosock, &toptr);
|
|
RNA_float_set(&toptr, "default_value", RNA_float_get(&fromptr, "default_value"));
|
|
}
|
|
|
|
for (bNode *node = (bNode *)ntree->nodes.first; node; node = node->next) {
|
|
if (node->type == SH_NODE_UVALONGSTROKE) {
|
|
// UV output of the UV Along Stroke node
|
|
bNodeSocket *sock = (bNodeSocket *)BLI_findlink(&node->outputs, 0);
|
|
|
|
// add new UV Map node
|
|
bNode *input_uvmap = nodeAddStaticNode(nullptr, ntree, SH_NODE_UVMAP);
|
|
input_uvmap->locx = node->locx - 200.0f;
|
|
input_uvmap->locy = node->locy;
|
|
NodeShaderUVMap *storage = (NodeShaderUVMap *)input_uvmap->storage;
|
|
if (node->custom1 & 1) { // use_tips
|
|
BLI_strncpy(storage->uv_map, uvNames[1], sizeof(storage->uv_map));
|
|
}
|
|
else {
|
|
BLI_strncpy(storage->uv_map, uvNames[0], sizeof(storage->uv_map));
|
|
}
|
|
fromsock = (bNodeSocket *)BLI_findlink(&input_uvmap->outputs, 0); // UV
|
|
|
|
// replace links from the UV Along Stroke node by links from the UV Map node
|
|
for (bNodeLink *link = (bNodeLink *)ntree->links.first; link; link = link->next) {
|
|
if (link->fromnode == node && link->fromsock == sock) {
|
|
nodeAddLink(ntree, input_uvmap, fromsock, link->tonode, link->tosock);
|
|
}
|
|
}
|
|
nodeRemSocketLinks(ntree, sock);
|
|
}
|
|
}
|
|
}
|
|
|
|
nodeSetActive(ntree, output_material);
|
|
BKE_ntree_update_main_tree(bmain, ntree, nullptr);
|
|
|
|
return ma;
|
|
}
|
|
|
|
void BlenderStrokeRenderer::RenderStrokeRep(StrokeRep *iStrokeRep) const
|
|
{
|
|
RenderStrokeRepBasic(iStrokeRep);
|
|
}
|
|
|
|
void BlenderStrokeRenderer::RenderStrokeRepBasic(StrokeRep *iStrokeRep) const
|
|
{
|
|
bNodeTree *nt = iStrokeRep->getNodeTree();
|
|
Material *ma = (Material *)BLI_ghash_lookup(_nodetree_hash, nt);
|
|
if (!ma) {
|
|
ma = BlenderStrokeRenderer::GetStrokeShader(freestyle_bmain, nt, false);
|
|
BLI_ghash_insert(_nodetree_hash, nt, ma);
|
|
}
|
|
iStrokeRep->setMaterial(ma);
|
|
|
|
const vector<Strip *> &strips = iStrokeRep->getStrips();
|
|
const bool hasTex = iStrokeRep->hasTex();
|
|
int totvert = 0, totedge = 0, totpoly = 0, totloop = 0;
|
|
int visible_faces, visible_segments;
|
|
for (vector<Strip *>::const_iterator s = strips.begin(), send = strips.end(); s != send; ++s) {
|
|
Strip::vertex_container &strip_vertices = (*s)->vertices();
|
|
|
|
// count visible faces and strip segments
|
|
test_strip_visibility(strip_vertices, &visible_faces, &visible_segments);
|
|
if (visible_faces == 0) {
|
|
continue;
|
|
}
|
|
|
|
totvert += visible_faces + visible_segments * 2;
|
|
totedge += visible_faces * 2 + visible_segments;
|
|
totpoly += visible_faces;
|
|
totloop += visible_faces * 3;
|
|
}
|
|
|
|
BlenderStrokeRenderer *self = const_cast<BlenderStrokeRenderer *>(this); // FIXME
|
|
vector<StrokeGroup *> *groups = hasTex ? &self->texturedStrokeGroups : &self->strokeGroups;
|
|
StrokeGroup *group;
|
|
if (groups->empty() || !(groups->back()->totvert + totvert < MESH_MAX_VERTS &&
|
|
groups->back()->materials.size() + 1 < MAXMAT)) {
|
|
group = new StrokeGroup;
|
|
groups->push_back(group);
|
|
}
|
|
else {
|
|
group = groups->back();
|
|
}
|
|
group->strokes.push_back(iStrokeRep);
|
|
group->totvert += totvert;
|
|
group->totedge += totedge;
|
|
group->totpoly += totpoly;
|
|
group->totloop += totloop;
|
|
|
|
if (!group->materials.contains(ma)) {
|
|
group->materials.add_new(ma, group->materials.size());
|
|
}
|
|
}
|
|
|
|
// Check if the triangle is visible (i.e., within the render image boundary)
|
|
bool BlenderStrokeRenderer::test_triangle_visibility(StrokeVertexRep *svRep[3]) const
|
|
{
|
|
int xl, xu, yl, yu;
|
|
Vec2r p;
|
|
|
|
xl = xu = yl = yu = 0;
|
|
for (int i = 0; i < 3; i++) {
|
|
p = svRep[i]->point2d();
|
|
if (p[0] < 0.0) {
|
|
xl++;
|
|
}
|
|
else if (p[0] > _width) {
|
|
xu++;
|
|
}
|
|
if (p[1] < 0.0) {
|
|
yl++;
|
|
}
|
|
else if (p[1] > _height) {
|
|
yu++;
|
|
}
|
|
}
|
|
return !(xl == 3 || xu == 3 || yl == 3 || yu == 3);
|
|
}
|
|
|
|
// Check the visibility of faces and strip segments.
|
|
void BlenderStrokeRenderer::test_strip_visibility(Strip::vertex_container &strip_vertices,
|
|
int *visible_faces,
|
|
int *visible_segments) const
|
|
{
|
|
const int strip_vertex_count = strip_vertices.size();
|
|
Strip::vertex_container::iterator v[3];
|
|
StrokeVertexRep *svRep[3];
|
|
bool visible;
|
|
|
|
/* Iterate over all vertices and count visible faces and strip segments
|
|
* (NOTE: a strip segment is a series of visible faces, while two strip
|
|
* segments are separated by one or more invisible faces). */
|
|
v[0] = strip_vertices.begin();
|
|
v[1] = v[0] + 1;
|
|
v[2] = v[0] + 2;
|
|
*visible_faces = *visible_segments = 0;
|
|
visible = false;
|
|
for (int n = 2; n < strip_vertex_count; n++, v[0]++, v[1]++, v[2]++) {
|
|
svRep[0] = *(v[0]);
|
|
svRep[1] = *(v[1]);
|
|
svRep[2] = *(v[2]);
|
|
if (test_triangle_visibility(svRep)) {
|
|
(*visible_faces)++;
|
|
if (!visible) {
|
|
(*visible_segments)++;
|
|
}
|
|
visible = true;
|
|
}
|
|
else {
|
|
visible = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release allocated memory for stroke groups
|
|
void BlenderStrokeRenderer::FreeStrokeGroups()
|
|
{
|
|
vector<StrokeGroup *>::const_iterator it, itend;
|
|
|
|
for (it = strokeGroups.begin(), itend = strokeGroups.end(); it != itend; ++it) {
|
|
delete (*it);
|
|
}
|
|
for (it = texturedStrokeGroups.begin(), itend = texturedStrokeGroups.end(); it != itend; ++it) {
|
|
delete (*it);
|
|
}
|
|
}
|
|
|
|
// Build a scene populated by mesh objects representing stylized strokes
|
|
int BlenderStrokeRenderer::GenerateScene()
|
|
{
|
|
vector<StrokeGroup *>::const_iterator it, itend;
|
|
|
|
for (it = strokeGroups.begin(), itend = strokeGroups.end(); it != itend; ++it) {
|
|
GenerateStrokeMesh(*it, false);
|
|
}
|
|
for (it = texturedStrokeGroups.begin(), itend = texturedStrokeGroups.end(); it != itend; ++it) {
|
|
GenerateStrokeMesh(*it, true);
|
|
}
|
|
return get_stroke_count();
|
|
}
|
|
|
|
// Return the number of strokes
|
|
int BlenderStrokeRenderer::get_stroke_count() const
|
|
{
|
|
return strokeGroups.size() + texturedStrokeGroups.size();
|
|
}
|
|
|
|
// Build a mesh object representing a group of stylized strokes
|
|
void BlenderStrokeRenderer::GenerateStrokeMesh(StrokeGroup *group, bool hasTex)
|
|
{
|
|
#if 0
|
|
Object *object_mesh = BKE_object_add(
|
|
freestyle_bmain, (ViewLayer *)freestyle_scene->view_layers.first, OB_MESH);
|
|
DEG_relations_tag_update(freestyle_bmain);
|
|
#else
|
|
Object *object_mesh = NewMesh();
|
|
#endif
|
|
Mesh *mesh = (Mesh *)object_mesh->data;
|
|
|
|
mesh->totvert = group->totvert;
|
|
mesh->totedge = group->totedge;
|
|
mesh->totpoly = group->totpoly;
|
|
mesh->totloop = group->totloop;
|
|
mesh->totcol = group->materials.size();
|
|
|
|
mesh->mvert = (MVert *)CustomData_add_layer(
|
|
&mesh->vdata, CD_MVERT, CD_SET_DEFAULT, nullptr, mesh->totvert);
|
|
mesh->medge = (MEdge *)CustomData_add_layer(
|
|
&mesh->edata, CD_MEDGE, CD_SET_DEFAULT, nullptr, mesh->totedge);
|
|
mesh->mpoly = (MPoly *)CustomData_add_layer(
|
|
&mesh->pdata, CD_MPOLY, CD_SET_DEFAULT, nullptr, mesh->totpoly);
|
|
mesh->mloop = (MLoop *)CustomData_add_layer(
|
|
&mesh->ldata, CD_MLOOP, CD_SET_DEFAULT, nullptr, mesh->totloop);
|
|
int *material_indices = (int *)CustomData_add_layer_named(
|
|
&mesh->pdata, CD_PROP_INT32, CD_SET_DEFAULT, nullptr, mesh->totpoly, "material_index");
|
|
MVert *vertices = mesh->mvert;
|
|
MEdge *edges = mesh->medge;
|
|
MPoly *polys = mesh->mpoly;
|
|
MLoop *loops = mesh->mloop;
|
|
MLoopUV *loopsuv[2] = {nullptr};
|
|
|
|
if (hasTex) {
|
|
// First UV layer
|
|
CustomData_add_layer_named(
|
|
&mesh->ldata, CD_MLOOPUV, CD_SET_DEFAULT, nullptr, mesh->totloop, uvNames[0]);
|
|
CustomData_set_layer_active(&mesh->ldata, CD_MLOOPUV, 0);
|
|
BKE_mesh_update_customdata_pointers(mesh, true);
|
|
loopsuv[0] = mesh->mloopuv;
|
|
|
|
// Second UV layer
|
|
CustomData_add_layer_named(
|
|
&mesh->ldata, CD_MLOOPUV, CD_SET_DEFAULT, nullptr, mesh->totloop, uvNames[1]);
|
|
CustomData_set_layer_active(&mesh->ldata, CD_MLOOPUV, 1);
|
|
BKE_mesh_update_customdata_pointers(mesh, true);
|
|
loopsuv[1] = mesh->mloopuv;
|
|
}
|
|
|
|
// colors and transparency (the latter represented by grayscale colors)
|
|
MLoopCol *colors = (MLoopCol *)CustomData_add_layer_named(
|
|
&mesh->ldata, CD_PROP_BYTE_COLOR, CD_SET_DEFAULT, nullptr, mesh->totloop, "Color");
|
|
MLoopCol *transp = (MLoopCol *)CustomData_add_layer_named(
|
|
&mesh->ldata, CD_PROP_BYTE_COLOR, CD_SET_DEFAULT, nullptr, mesh->totloop, "Alpha");
|
|
mesh->mloopcol = colors;
|
|
|
|
mesh->mat = (Material **)MEM_mallocN(sizeof(Material *) * mesh->totcol, "MaterialList");
|
|
for (const auto item : group->materials.items()) {
|
|
Material *material = item.key;
|
|
const int matnr = item.value;
|
|
mesh->mat[matnr] = material;
|
|
if (material) {
|
|
id_us_plus(&material->id);
|
|
}
|
|
}
|
|
|
|
////////////////////
|
|
// Data copy
|
|
////////////////////
|
|
|
|
int vertex_index = 0, edge_index = 0, loop_index = 0;
|
|
int visible_faces, visible_segments;
|
|
bool visible;
|
|
Strip::vertex_container::iterator v[3];
|
|
StrokeVertexRep *svRep[3];
|
|
Vec2r p;
|
|
|
|
for (vector<StrokeRep *>::const_iterator it = group->strokes.begin(),
|
|
itend = group->strokes.end();
|
|
it != itend;
|
|
++it) {
|
|
const int matnr = group->materials.lookup_default((*it)->getMaterial(), 0);
|
|
|
|
vector<Strip *> &strips = (*it)->getStrips();
|
|
for (vector<Strip *>::const_iterator s = strips.begin(), send = strips.end(); s != send; ++s) {
|
|
Strip::vertex_container &strip_vertices = (*s)->vertices();
|
|
int strip_vertex_count = strip_vertices.size();
|
|
|
|
// count visible faces and strip segments
|
|
test_strip_visibility(strip_vertices, &visible_faces, &visible_segments);
|
|
if (visible_faces == 0) {
|
|
continue;
|
|
}
|
|
|
|
v[0] = strip_vertices.begin();
|
|
v[1] = v[0] + 1;
|
|
v[2] = v[0] + 2;
|
|
|
|
visible = false;
|
|
|
|
// NOTE: Mesh generation in the following loop assumes stroke strips
|
|
// to be triangle strips.
|
|
for (int n = 2; n < strip_vertex_count; n++, v[0]++, v[1]++, v[2]++) {
|
|
svRep[0] = *(v[0]);
|
|
svRep[1] = *(v[1]);
|
|
svRep[2] = *(v[2]);
|
|
if (!test_triangle_visibility(svRep)) {
|
|
visible = false;
|
|
}
|
|
else {
|
|
if (!visible) {
|
|
// first vertex
|
|
vertices->co[0] = svRep[0]->point2d()[0];
|
|
vertices->co[1] = svRep[0]->point2d()[1];
|
|
vertices->co[2] = get_stroke_vertex_z();
|
|
|
|
++vertices;
|
|
++vertex_index;
|
|
|
|
// second vertex
|
|
vertices->co[0] = svRep[1]->point2d()[0];
|
|
vertices->co[1] = svRep[1]->point2d()[1];
|
|
vertices->co[2] = get_stroke_vertex_z();
|
|
|
|
++vertices;
|
|
++vertex_index;
|
|
|
|
// first edge
|
|
edges->v1 = vertex_index - 2;
|
|
edges->v2 = vertex_index - 1;
|
|
++edges;
|
|
++edge_index;
|
|
}
|
|
visible = true;
|
|
|
|
// vertex
|
|
vertices->co[0] = svRep[2]->point2d()[0];
|
|
vertices->co[1] = svRep[2]->point2d()[1];
|
|
vertices->co[2] = get_stroke_vertex_z();
|
|
++vertices;
|
|
++vertex_index;
|
|
|
|
// edges
|
|
edges->v1 = vertex_index - 1;
|
|
edges->v2 = vertex_index - 3;
|
|
++edges;
|
|
++edge_index;
|
|
|
|
edges->v1 = vertex_index - 1;
|
|
edges->v2 = vertex_index - 2;
|
|
++edges;
|
|
++edge_index;
|
|
|
|
// poly
|
|
polys->loopstart = loop_index;
|
|
polys->totloop = 3;
|
|
*material_indices = matnr;
|
|
++material_indices;
|
|
++polys;
|
|
|
|
// Even and odd loops connect triangles vertices differently
|
|
bool is_odd = n % 2;
|
|
// loops
|
|
if (is_odd) {
|
|
loops[0].v = vertex_index - 1;
|
|
loops[0].e = edge_index - 2;
|
|
|
|
loops[1].v = vertex_index - 3;
|
|
loops[1].e = edge_index - 3;
|
|
|
|
loops[2].v = vertex_index - 2;
|
|
loops[2].e = edge_index - 1;
|
|
}
|
|
else {
|
|
loops[0].v = vertex_index - 1;
|
|
loops[0].e = edge_index - 1;
|
|
|
|
loops[1].v = vertex_index - 2;
|
|
loops[1].e = edge_index - 3;
|
|
|
|
loops[2].v = vertex_index - 3;
|
|
loops[2].e = edge_index - 2;
|
|
}
|
|
loops += 3;
|
|
loop_index += 3;
|
|
|
|
// UV
|
|
if (hasTex) {
|
|
// First UV layer (loopsuv[0]) has no tips (texCoord(0)).
|
|
// Second UV layer (loopsuv[1]) has tips: (texCoord(1)).
|
|
for (int L = 0; L < 2; L++) {
|
|
if (is_odd) {
|
|
loopsuv[L][0].uv[0] = svRep[2]->texCoord(L).x();
|
|
loopsuv[L][0].uv[1] = svRep[2]->texCoord(L).y();
|
|
|
|
loopsuv[L][1].uv[0] = svRep[0]->texCoord(L).x();
|
|
loopsuv[L][1].uv[1] = svRep[0]->texCoord(L).y();
|
|
|
|
loopsuv[L][2].uv[0] = svRep[1]->texCoord(L).x();
|
|
loopsuv[L][2].uv[1] = svRep[1]->texCoord(L).y();
|
|
}
|
|
else {
|
|
loopsuv[L][0].uv[0] = svRep[2]->texCoord(L).x();
|
|
loopsuv[L][0].uv[1] = svRep[2]->texCoord(L).y();
|
|
|
|
loopsuv[L][1].uv[0] = svRep[1]->texCoord(L).x();
|
|
loopsuv[L][1].uv[1] = svRep[1]->texCoord(L).y();
|
|
|
|
loopsuv[L][2].uv[0] = svRep[0]->texCoord(L).x();
|
|
loopsuv[L][2].uv[1] = svRep[0]->texCoord(L).y();
|
|
}
|
|
loopsuv[L] += 3;
|
|
}
|
|
}
|
|
|
|
// colors and alpha transparency. vertex colors are in sRGB
|
|
// space by convention, so convert from linear
|
|
float rgba[3][4];
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
copy_v3fl_v3db(rgba[i], &svRep[i]->color()[0]);
|
|
rgba[i][3] = svRep[i]->alpha();
|
|
}
|
|
|
|
if (is_odd) {
|
|
linearrgb_to_srgb_uchar4(&colors[0].r, rgba[2]);
|
|
linearrgb_to_srgb_uchar4(&colors[1].r, rgba[0]);
|
|
linearrgb_to_srgb_uchar4(&colors[2].r, rgba[1]);
|
|
}
|
|
else {
|
|
linearrgb_to_srgb_uchar4(&colors[0].r, rgba[2]);
|
|
linearrgb_to_srgb_uchar4(&colors[1].r, rgba[1]);
|
|
linearrgb_to_srgb_uchar4(&colors[2].r, rgba[0]);
|
|
}
|
|
transp[0].r = transp[0].g = transp[0].b = colors[0].a;
|
|
transp[1].r = transp[1].g = transp[1].b = colors[1].a;
|
|
transp[2].r = transp[2].g = transp[2].b = colors[2].a;
|
|
colors += 3;
|
|
transp += 3;
|
|
}
|
|
} // loop over strip vertices
|
|
} // loop over strips
|
|
} // loop over strokes
|
|
|
|
BKE_object_materials_test(freestyle_bmain, object_mesh, (ID *)mesh);
|
|
BKE_mesh_normals_tag_dirty(mesh);
|
|
|
|
#if 0 // XXX
|
|
BLI_assert(mesh->totvert == vertex_index);
|
|
BLI_assert(mesh->totedge == edge_index);
|
|
BLI_assert(mesh->totloop == loop_index);
|
|
BKE_mesh_validate(mesh, true, true);
|
|
#endif
|
|
}
|
|
|
|
// A replacement of BKE_object_add() for better performance.
|
|
Object *BlenderStrokeRenderer::NewMesh() const
|
|
{
|
|
Object *ob;
|
|
char name[MAX_ID_NAME];
|
|
unsigned int mesh_id = get_stroke_mesh_id();
|
|
|
|
BLI_snprintf(name, MAX_ID_NAME, "0%08xOB", mesh_id);
|
|
ob = BKE_object_add_only_object(freestyle_bmain, OB_MESH, name);
|
|
BLI_snprintf(name, MAX_ID_NAME, "0%08xME", mesh_id);
|
|
ob->data = BKE_mesh_add(freestyle_bmain, name);
|
|
|
|
Collection *collection_master = freestyle_scene->master_collection;
|
|
BKE_collection_object_add(freestyle_bmain, collection_master, ob);
|
|
DEG_graph_tag_relations_update(freestyle_depsgraph);
|
|
|
|
DEG_graph_id_tag_update(freestyle_bmain,
|
|
freestyle_depsgraph,
|
|
&ob->id,
|
|
ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
|
|
|
|
return ob;
|
|
}
|
|
|
|
Render *BlenderStrokeRenderer::RenderScene(Render * /*re*/, bool render)
|
|
{
|
|
Camera *camera = (Camera *)freestyle_scene->camera->data;
|
|
if (camera->clip_end < _z) {
|
|
camera->clip_end = _z + _z_delta * 100.0f;
|
|
}
|
|
#if 0
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
cout << "clip_start " << camera->clip_start << ", clip_end " << camera->clip_end << endl;
|
|
}
|
|
#endif
|
|
|
|
Render *freestyle_render = RE_NewSceneRender(freestyle_scene);
|
|
DEG_graph_relations_update(freestyle_depsgraph);
|
|
|
|
RE_RenderFreestyleStrokes(
|
|
freestyle_render, freestyle_bmain, freestyle_scene, render && get_stroke_count() > 0);
|
|
|
|
return freestyle_render;
|
|
}
|
|
|
|
} /* namespace Freestyle */
|