Panda3D
daeMaterials.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file daeMaterials.cxx
10  * @author rdb
11  * @date 2008-10-03
12  */
13 
14 #include "daeMaterials.h"
15 #include "config_daeegg.h"
16 #include "fcollada_utils.h"
17 
18 #include <FCDocument/FCDocument.h>
19 #include <FCDocument/FCDMaterial.h>
20 #include <FCDocument/FCDEffect.h>
21 #include <FCDocument/FCDTexture.h>
22 #include <FCDocument/FCDEffectParameterSampler.h>
23 #include <FCDocument/FCDImage.h>
24 
25 #include "filename.h"
26 #include "string_utils.h"
27 
28 using std::endl;
29 using std::string;
30 
31 TypeHandle DaeMaterials::_type_handle;
32 
33 // luminance function, based on the ISOCIE color standards see ITU-R
34 // Recommendation BT.709-4
35 #define luminance(c) ((c[0] * 0.212671 + c[1] * 0.715160 + c[2] * 0.072169))
36 
37 /**
38  *
39  */
40 DaeMaterials::
41 DaeMaterials(const FCDGeometryInstance* geometry_instance) {
42  for (size_t mi = 0; mi < geometry_instance->GetMaterialInstanceCount(); ++mi) {
43  add_material_instance(geometry_instance->GetMaterialInstance(mi));
44  }
45 }
46 
47 /**
48  * Adds a material instance. Normally automatically done by constructor.
49  */
50 void DaeMaterials::add_material_instance(const FCDMaterialInstance* instance) {
51  nassertv(instance != nullptr);
52  const string semantic (FROM_FSTRING(instance->GetSemantic()));
53  if (_materials.count(semantic) > 0) {
54  daeegg_cat.warning() << "Ignoring duplicate material with semantic " << semantic << endl;
55  return;
56  }
57  _materials[semantic] = new DaeMaterial();
58 
59  // Load in the uvsets
60  for (size_t vib = 0; vib < instance->GetVertexInputBindingCount(); ++vib) {
61  const FCDMaterialInstanceBindVertexInput* mivib = instance->GetVertexInputBinding(vib);
62  assert(mivib != nullptr);
63  PT(DaeVertexInputBinding) bvi = new DaeVertexInputBinding();
64  bvi->_input_set = mivib->inputSet;
65 #if FCOLLADA_VERSION >= 0x00030005
66  bvi->_input_semantic = mivib->GetInputSemantic();
67  bvi->_semantic = *mivib->semantic;
68 #else
69  bvi->_input_semantic = mivib->inputSemantic;
70  bvi->_semantic = FROM_FSTRING(mivib->semantic);
71 #endif
72  _materials[semantic]->_uvsets.push_back(bvi);
73  }
74 
75  // Handle the material stuff
76  daeegg_cat.spam() << "Trying to process material with semantic " << semantic << endl;
77  PT_EggMaterial egg_material = new EggMaterial(semantic);
78  pvector<PT_EggTexture> egg_textures;
79  const FCDEffect* effect = instance->GetMaterial()->GetEffect();
80  if (effect == nullptr) {
81  daeegg_cat.debug() << "Ignoring material (semantic: " << semantic << ") without assigned effect" << endl;
82  } else {
83  // Grab the common profile effect
84  const FCDEffectStandard* effect_common = (FCDEffectStandard *)effect->FindProfile(FUDaeProfileType::COMMON);
85  if (effect_common == nullptr) {
86  daeegg_cat.info() << "Ignoring effect referenced by material with semantic " << semantic
87  << " because it has no common profile" << endl;
88  } else {
89  daeegg_cat.spam() << "Processing effect, material semantic is " << semantic << endl;
90  // Set the material parameters
91  egg_material->set_amb(TO_COLOR(effect_common->GetAmbientColor()));
92  // We already process transparency using blend modes LVecBase4 diffuse =
93  // TO_COLOR(effect_common->GetDiffuseColor());
94  // diffuse.set_w(diffuse.get_w() * (1.0f -
95  // effect_common->GetOpacity())); egg_material->set_diff(diffuse);
96  egg_material->set_diff(TO_COLOR(effect_common->GetDiffuseColor()));
97  egg_material->set_emit(TO_COLOR(effect_common->GetEmissionColor()) * effect_common->GetEmissionFactor());
98  egg_material->set_shininess(effect_common->GetShininess());
99  egg_material->set_spec(TO_COLOR(effect_common->GetSpecularColor()));
100  // Now try to load in the textures
101  process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::DIFFUSE, EggTexture::ET_modulate);
102  process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::BUMP, EggTexture::ET_normal);
103  process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::SPECULAR, EggTexture::ET_modulate_gloss);
104  process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::SPECULAR_LEVEL, EggTexture::ET_gloss);
105  process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::TRANSPARENT, EggTexture::ET_unspecified, EggTexture::F_alpha);
106  process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::EMISSION, EggTexture::ET_add);
107 #if FCOLLADA_VERSION < 0x00030005
108  process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::OPACITY, EggTexture::ET_unspecified, EggTexture::F_alpha);
109 #endif
110  // Now, calculate the color blend stuff.
111  _materials[semantic]->_blend = convert_blend(effect_common->GetTransparencyMode(),
112  TO_COLOR(effect_common->GetTranslucencyColor()),
113  effect_common->GetTranslucencyFactor());
114  }
115  // Find an <extra> tag to support some extra stuff from extensions
116  process_extra(semantic, effect->GetExtra());
117  }
118  daeegg_cat.spam() << "Found " << egg_textures.size() << " textures in material" << endl;
119  _materials[semantic]->_egg_material = egg_material;
120 }
121 
122 /**
123  * Processes the given texture bucket and gives the textures in it the given
124  * envtype and format.
125  */
126 void DaeMaterials::
127 process_texture_bucket(const string semantic, const FCDEffectStandard* effect_common, FUDaeTextureChannel::Channel bucket, EggTexture::EnvType envtype, EggTexture::Format format) {
128  for (size_t tx = 0; tx < effect_common->GetTextureCount(bucket); ++tx) {
129  const FCDImage* image = effect_common->GetTexture(bucket, tx)->GetImage();
130  if (image == nullptr) {
131  daeegg_cat.warning() << "Texture references a nonexisting image!" << endl;
132  } else {
133  const FCDEffectParameterSampler* sampler = effect_common->GetTexture(bucket, tx)->GetSampler();
134  // FCollada only supplies absolute paths. We need to grab the document
135  // location ourselves and make the image path absolute.
136  Filename texpath;
137  if (image->GetDocument()) {
138  Filename docpath = Filename::from_os_specific(FROM_FSTRING(image->GetDocument()->GetFileUrl()));
139  docpath.make_canonical();
140  texpath = Filename::from_os_specific(FROM_FSTRING(image->GetFilename()));
141  texpath.make_canonical();
142  texpath.make_relative_to(docpath.get_dirname(), true);
143  daeegg_cat.debug() << "Found texture with path " << texpath << endl;
144  } else {
145  // Never mind.
146  texpath = Filename::from_os_specific(FROM_FSTRING(image->GetFilename()));
147  }
148  PT_EggTexture egg_texture = new EggTexture(FROM_FSTRING(image->GetDaeId()), texpath.to_os_generic());
149  // Find a set of UV coordinates
150  const FCDEffectParameterInt* uvset = effect_common->GetTexture(bucket, tx)->GetSet();
151  if (uvset != nullptr) {
152  daeegg_cat.debug() << "Texture has uv name '" << FROM_FSTRING(uvset->GetSemantic()) << "'\n";
153  string uvset_semantic (FROM_FSTRING(uvset->GetSemantic()));
154 
155  // Only set the UV name if this UV set actually exists.
156  for (size_t i = 0; i < _materials[semantic]->_uvsets.size(); ++i) {
157  if (_materials[semantic]->_uvsets[i]->_semantic == uvset_semantic) {
158  egg_texture->set_uv_name(uvset_semantic);
159  break;
160  }
161  }
162  }
163  // Apply sampler stuff
164  if (sampler != nullptr) {
165  egg_texture->set_texture_type(convert_texture_type(sampler->GetSamplerType()));
166  egg_texture->set_wrap_u(convert_wrap_mode(sampler->GetWrapS()));
167  if (sampler->GetSamplerType() != FCDEffectParameterSampler::SAMPLER1D) {
168  egg_texture->set_wrap_v(convert_wrap_mode(sampler->GetWrapT()));
169  }
170  if (sampler->GetSamplerType() == FCDEffectParameterSampler::SAMPLER3D) {
171  egg_texture->set_wrap_w(convert_wrap_mode(sampler->GetWrapP()));
172  }
173  egg_texture->set_minfilter(convert_filter_type(sampler->GetMinFilter()));
174  egg_texture->set_magfilter(convert_filter_type(sampler->GetMagFilter()));
175  if (envtype != EggTexture::ET_unspecified) {
176  egg_texture->set_env_type(envtype);
177  }
178  if (format != EggTexture::F_unspecified) {
179  egg_texture->set_format(format);
180  }
181  }
182  _materials[semantic]->_egg_textures.push_back(egg_texture);
183  }
184  }
185 }
186 
187 /**
188  * Processes the extra data in the given <extra> tag. If the given element is
189  * NULL, it just silently returns.
190  */
191 void DaeMaterials::
192 process_extra(const string semantic, const FCDExtra* extra) {
193  if (extra == nullptr) return;
194  const FCDEType* etype = extra->GetDefaultType();
195  if (etype == nullptr) return;
196  for (size_t et = 0; et < etype->GetTechniqueCount(); ++et) {
197  const FCDENode* enode = ((const FCDENode*)(etype->GetTechnique(et)))->FindChildNode("double_sided");
198  if (enode != nullptr) {
199  string content = trim(enode->GetContent());
200  if (content == "1" || content == "true") {
201  _materials[semantic]->_double_sided = true;
202  } else if (content == "0" || content == "false") {
203  _materials[semantic]->_double_sided = false;
204  } else {
205  daeegg_cat.warning() << "Expected <double_sided> tag to be either 1 or 0, found '" << content << "' instead" << endl;
206  }
207  }
208  }
209 }
210 
211 /**
212  * Applies the stuff to the given EggPrimitive.
213  */
214 void DaeMaterials::
215 apply_to_primitive(const string semantic, const PT(EggPrimitive) to) {
216  if (_materials.count(semantic) > 0) {
217  to->set_material(_materials[semantic]->_egg_material);
218  for (pvector<PT_EggTexture>::iterator it = _materials[semantic]->_egg_textures.begin(); it != _materials[semantic]->_egg_textures.end(); ++it) {
219  daeegg_cat.spam() << "Applying texture " << (*it)->get_name() << " from material with semantic " << semantic << endl;
220  to->add_texture(*it);
221  }
222  to->set_bface_flag(_materials[semantic]->_double_sided);
223  }
224 }
225 
226 /**
227  * Applies the colorblend stuff to the given EggGroup.
228  */
229 void DaeMaterials::
230 apply_to_group(const string semantic, const PT(EggGroup) to, bool invert_transparency) {
231  if (_materials.count(semantic) > 0) {
232  PT(DaeBlendSettings) blend = _materials[semantic]->_blend;
233  if (blend && blend->_enabled) {
234  to->set_blend_mode(EggGroup::BM_add);
235  to->set_blend_color(blend->_color);
236  if (invert_transparency) {
237  to->set_blend_operand_a(blend->_operand_b);
238  to->set_blend_operand_b(blend->_operand_a);
239  } else {
240  to->set_blend_operand_a(blend->_operand_a);
241  to->set_blend_operand_b(blend->_operand_b);
242  }
243  } else if (blend && invert_transparency) {
244  to->set_blend_mode(EggGroup::BM_add);
245  to->set_blend_color(blend->_color);
246  to->set_blend_operand_a(blend->_operand_b);
247  to->set_blend_operand_b(blend->_operand_a);
248  }
249  }
250 }
251 
252 /**
253  * Returns the semantic of the uvset with the specified input set, or an empty
254  * string if the given material has no input set.
255  */
256 const string DaeMaterials::
257 get_uvset_name(const string semantic, FUDaeGeometryInput::Semantic input_semantic, int32 input_set) {
258  if (_materials.count(semantic) > 0) {
259  if (input_set == -1 && _materials[semantic]->_uvsets.size() == 1) {
260  return _materials[semantic]->_uvsets[0]->_semantic;
261  } else {
262  for (size_t i = 0; i < _materials[semantic]->_uvsets.size(); ++i) {
263  if (_materials[semantic]->_uvsets[i]->_input_set == input_set &&
264  _materials[semantic]->_uvsets[i]->_input_semantic == input_semantic) {
265  return _materials[semantic]->_uvsets[i]->_semantic;
266  }
267  }
268  // If we can't find it, let's look again, but don't care for the
269  // input_semantic this time. The reason for this is that some tools
270  // export textangents and texbinormals bound to a uvset with input
271  // semantic TEXCOORD.
272  for (size_t i = 0; i < _materials[semantic]->_uvsets.size(); ++i) {
273  if (_materials[semantic]->_uvsets[i]->_input_set == input_set) {
274  daeegg_cat.debug() << "Using uv set with non-matching input semantic " << _materials[semantic]->_uvsets[i]->_semantic << "\n";
275  return _materials[semantic]->_uvsets[i]->_semantic;
276  }
277  }
278  daeegg_cat.debug() << "No uv set binding found for input set " << input_set << "\n";
279  }
280  }
281  return "";
282 }
283 
284 /**
285  * Converts an FCollada sampler type to the EggTexture texture type
286  * equivalent.
287  */
288 EggTexture::TextureType DaeMaterials::
289 convert_texture_type(const FCDEffectParameterSampler::SamplerType orig_type) {
290  switch (orig_type) {
291  case FCDEffectParameterSampler::SAMPLER1D:
292  return EggTexture::TT_1d_texture;
293  case FCDEffectParameterSampler::SAMPLER2D:
294  return EggTexture::TT_2d_texture;
295  case FCDEffectParameterSampler::SAMPLER3D:
296  return EggTexture::TT_3d_texture;
297  case FCDEffectParameterSampler::SAMPLERCUBE:
298  return EggTexture::TT_cube_map;
299  default:
300  daeegg_cat.warning() << "Invalid sampler type found" << endl;
301  }
302  return EggTexture::TT_unspecified;
303 }
304 
305 /**
306  * Converts an FCollada wrap mode to the EggTexture wrap mode equivalent.
307  */
308 EggTexture::WrapMode DaeMaterials::
309 convert_wrap_mode(const FUDaeTextureWrapMode::WrapMode orig_mode) {
310  switch (orig_mode) {
311  case FUDaeTextureWrapMode::NONE:
312  // FIXME: this shouldnt be unspecified
313  return EggTexture::WM_unspecified;
314  case FUDaeTextureWrapMode::WRAP:
315  return EggTexture::WM_repeat;
316  case FUDaeTextureWrapMode::MIRROR:
317  return EggTexture::WM_mirror;
318  case FUDaeTextureWrapMode::CLAMP:
319  return EggTexture::WM_clamp;
320  case FUDaeTextureWrapMode::BORDER:
321  return EggTexture::WM_border_color;
322  case FUDaeTextureWrapMode::UNKNOWN:
323  return EggTexture::WM_unspecified;
324  default:
325  daeegg_cat.warning() << "Invalid wrap mode found: " << FUDaeTextureWrapMode::ToString(orig_mode) << endl;
326  }
327  return EggTexture::WM_unspecified;
328 }
329 
330 /**
331  * Converts an FCollada filter function to the EggTexture wrap type
332  * equivalent.
333  */
334 EggTexture::FilterType DaeMaterials::
335 convert_filter_type(const FUDaeTextureFilterFunction::FilterFunction orig_type) {
336  switch (orig_type) {
337  case FUDaeTextureFilterFunction::NONE:
338  // FIXME: this shouldnt be unspecified
339  return EggTexture::FT_unspecified;
340  case FUDaeTextureFilterFunction::NEAREST:
341  return EggTexture::FT_nearest;
342  case FUDaeTextureFilterFunction::LINEAR:
343  return EggTexture::FT_linear;
344  case FUDaeTextureFilterFunction::NEAREST_MIPMAP_NEAREST:
345  return EggTexture::FT_nearest_mipmap_nearest;
346  case FUDaeTextureFilterFunction::LINEAR_MIPMAP_NEAREST:
347  return EggTexture::FT_linear_mipmap_nearest;
348  case FUDaeTextureFilterFunction::NEAREST_MIPMAP_LINEAR:
349  return EggTexture::FT_nearest_mipmap_linear;
350  case FUDaeTextureFilterFunction::LINEAR_MIPMAP_LINEAR:
351  return EggTexture::FT_linear_mipmap_linear;
352  case FUDaeTextureFilterFunction::UNKNOWN:
353  return EggTexture::FT_unspecified;
354  default:
355  daeegg_cat.warning() << "Unknown filter type found: " << FUDaeTextureFilterFunction::ToString(orig_type) << endl;
356  }
357  return EggTexture::FT_unspecified;
358 }
359 
360 /**
361  * Converts collada blend attribs to Panda's equivalents.
362  */
363 PT(DaeMaterials::DaeBlendSettings) DaeMaterials::
364 convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparent, double transparency) {
365  // Create the DaeBlendSettings and fill it with some defaults.
366  PT(DaeBlendSettings) blend = new DaeBlendSettings();
367  blend->_enabled = true;
368  blend->_color = LColor::zero();
369  blend->_operand_a = EggGroup::BO_unspecified;
370  blend->_operand_b = EggGroup::BO_unspecified;
371 
372  // First fill in the color value.
373  if (mode == FCDEffectStandard::A_ONE) {// || mode == FCDEffectStandard::A_ZERO) {
374  double value = transparent[3] * transparency;
375  blend->_color = LColor(value, value, value, value);
376  } else if (mode == FCDEffectStandard::RGB_ZERO) {//|| mode == FCDEffectStandard::RGB_ONE) {
377  blend->_color = transparent * transparency;
378  blend->_color[3] = luminance(blend->_color);
379  } else {
380  daeegg_cat.error() << "Unknown opaque type found!" << endl;
381  blend->_enabled = false;
382  return blend;
383  }
384 
385  // Now figure out the operands.
386  if (mode == FCDEffectStandard::RGB_ZERO) {// || mode == FCDEffectStandard::A_ZERO) {
387  blend->_operand_a = EggGroup::BO_one_minus_constant_color;
388  blend->_operand_b = EggGroup::BO_constant_color;
389  } else if (mode == FCDEffectStandard::A_ONE) {// || mode == FCDEffectStandard::RGB_ONE) {
390  blend->_operand_a = EggGroup::BO_constant_color;
391  blend->_operand_b = EggGroup::BO_one_minus_constant_color;
392  } else {
393  daeegg_cat.error() << "Unknown opaque type found!" << endl;
394  blend->_enabled = false;
395  return blend;
396  }
397 
398  // See if we can optimize out the color.
399  if (blend->_operand_a == EggGroup::BO_constant_color) {
400  if (blend->_color == LColor::zero()) {
401  blend->_operand_a = EggGroup::BO_zero;
402  } else if (blend->_color == LColor(1, 1, 1, 1)) {
403  blend->_operand_a = EggGroup::BO_one;
404  }
405  }
406  if (blend->_operand_b == EggGroup::BO_constant_color) {
407  if (blend->_color == LColor::zero()) {
408  blend->_operand_b = EggGroup::BO_zero;
409  } else if (blend->_color == LColor(1, 1, 1, 1)) {
410  blend->_operand_b = EggGroup::BO_one;
411  }
412  }
413  if (blend->_operand_a == EggGroup::BO_one_minus_constant_color) {
414  if (blend->_color == LColor::zero()) {
415  blend->_operand_a = EggGroup::BO_one;
416  } else if (blend->_color == LColor(1, 1, 1, 1)) {
417  blend->_operand_a = EggGroup::BO_zero;
418  }
419  }
420  if (blend->_operand_b == EggGroup::BO_one_minus_constant_color) {
421  if (blend->_color == LColor::zero()) {
422  blend->_operand_b = EggGroup::BO_one;
423  } else if (blend->_color == LColor(1, 1, 1, 1)) {
424  blend->_operand_b = EggGroup::BO_zero;
425  }
426  }
427 
428  // See if we can entirely disable the blend.
429  if (blend->_operand_a == EggGroup::BO_one && blend->_operand_b == EggGroup::BO_zero) {
430  blend->_enabled = false;
431  }
432  return blend;
433 }
A base class for any of a number of kinds of geometry primitives: polygons, point lights,...
Definition: eggPrimitive.h:47
std::string get_dirname() const
Returns the directory part of the filename.
Definition: filename.I:358
void add_texture(EggTexture *texture)
Applies the indicated texture to the primitive.
Definition: eggPrimitive.I:162
Defines a texture map that may be applied to geometry.
Definition: eggTexture.h:30
static EggTexture::FilterType convert_filter_type(const FUDaeTextureFilterFunction::FilterFunction orig_type)
Converts an FCollada filter function to the EggTexture wrap type equivalent.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
set_bface_flag
Sets the backfacing flag of the polygon.
Definition: eggPrimitive.h:116
void add_material_instance(const FCDMaterialInstance *instance)
Adds a material instance.
bool make_canonical()
Converts this filename to a canonical name by replacing the directory part with the fully-qualified d...
Definition: filename.cxx:1011
set_material
Applies the indicated material to the primitive.
Definition: eggPrimitive.h:115
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::string to_os_generic() const
This is similar to to_os_specific(), but it is designed to generate a filename that can be understood...
Definition: filename.cxx:1182
PT(DaeMaterials::DaeBlendSettings) DaeMaterials
Converts collada blend attribs to Panda's equivalents.
void apply_to_primitive(const std::string semantic, const PT(EggPrimitive) to)
Applies the stuff to the given EggPrimitive.
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition: eggGroup.h:34
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
static EggTexture::WrapMode convert_wrap_mode(const FUDaeTextureWrapMode::WrapMode orig_mode)
Converts an FCollada wrap mode to the EggTexture wrap mode equivalent.
static EggTexture::TextureType convert_texture_type(const FCDEffectParameterSampler::SamplerType orig_type)
Converts an FCollada sampler type to the EggTexture texture type equivalent.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
string trim(const string &str)
Returns a new string representing the contents of the given string with both leading and trailing whi...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool make_relative_to(Filename directory, bool allow_backups=true)
Adjusts this filename, which must be a fully-specified pathname beginning with a slash,...
Definition: filename.cxx:1640
const std::string get_uvset_name(const std::string semantic, FUDaeGeometryInput::Semantic input_semantic, int32 input_set)
Returns the semantic of the uvset with the specified input set, or an empty string if the given mater...
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
void apply_to_group(const std::string semantic, const PT(EggGroup) to, bool invert_transparency=false)
Applies the colorblend stuff to the given EggGroup.
static Filename from_os_specific(const std::string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes,...
Definition: filename.cxx:328