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  */
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  */
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 }
static EggTexture::TextureType convert_texture_type(const FCDEffectParameterSampler::SamplerType orig_type)
Converts an FCollada sampler type to the EggTexture texture type equivalent.
void add_material_instance(const FCDMaterialInstance *instance)
Adds a material instance.
static EggTexture::WrapMode convert_wrap_mode(const FUDaeTextureWrapMode::WrapMode orig_mode)
Converts an FCollada wrap mode to the EggTexture wrap mode equivalent.
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...
void apply_to_group(const std::string semantic, const PT(EggGroup) to, bool invert_transparency=false)
Applies the colorblend stuff to the given EggGroup.
void apply_to_primitive(const std::string semantic, const PT(EggPrimitive) to)
Applies the stuff to the given EggPrimitive.
static EggTexture::FilterType convert_filter_type(const FUDaeTextureFilterFunction::FilterFunction orig_type)
Converts an FCollada filter function to the EggTexture wrap type equivalent.
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition: eggGroup.h:34
A base class for any of a number of kinds of geometry primitives: polygons, point lights,...
Definition: eggPrimitive.h:49
set_bface_flag
Sets the backfacing flag of the polygon.
Definition: eggPrimitive.h:116
set_material
Applies the indicated material to the primitive.
Definition: eggPrimitive.h:115
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
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
bool make_canonical()
Converts this filename to a canonical name by replacing the directory part with the fully-qualified d...
Definition: filename.cxx:1011
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
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
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
std::string get_dirname() const
Returns the directory part of the filename.
Definition: filename.I:358
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PT(DaeMaterials::DaeBlendSettings) DaeMaterials
Converts collada blend attribs to Panda's equivalents.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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.