Panda3D
 All Classes Functions Variables Enumerations
cLwoSurface.cxx
1 // Filename: cLwoSurface.cxx
2 // Created by: drose (25Apr01)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "cLwoSurface.h"
16 #include "cLwoSurfaceBlock.h"
17 #include "cLwoClip.h"
18 #include "lwoToEggConverter.h"
19 
20 #include "lwoSurfaceColor.h"
21 #include "lwoSurfaceParameter.h"
22 #include "lwoSurfaceSmoothingAngle.h"
23 #include "lwoSurfaceSidedness.h"
24 #include "lwoSurfaceBlock.h"
25 #include "eggPrimitive.h"
26 #include "string_utils.h"
27 #include "mathNumbers.h"
28 #include "dcast.h"
29 
30 
31 ////////////////////////////////////////////////////////////////////
32 // Function: CLwoSurface::Constructor
33 // Access: Public
34 // Description:
35 ////////////////////////////////////////////////////////////////////
36 CLwoSurface::
37 CLwoSurface(LwoToEggConverter *converter, const LwoSurface *surface) :
38  _converter(converter),
39  _surface(surface)
40 {
41  _flags = 0;
42  _rgb.set(1.0, 1.0, 1.0);
43  _checked_material = false;
44  _checked_texture = false;
45  _map_uvs = NULL;
46  _block = (CLwoSurfaceBlock *)NULL;
47 
48  // Walk through the chunk list, looking for some basic properties.
49  int num_chunks = _surface->get_num_chunks();
50  for (int i = 0; i < num_chunks; i++) {
51  const IffChunk *chunk = _surface->get_chunk(i);
52 
53  if (chunk->is_of_type(LwoSurfaceColor::get_class_type())) {
54  const LwoSurfaceColor *color = DCAST(LwoSurfaceColor, chunk);
55  _flags |= F_rgb;
56  _rgb = color->_color;
57 
58  } else if (chunk->is_of_type(LwoSurfaceParameter::get_class_type())) {
59  const LwoSurfaceParameter *param = DCAST(LwoSurfaceParameter, chunk);
60  IffId type = param->get_id();
61 
62  if (type == IffId("DIFF")) {
63  _flags |= F_diffuse;
64  _diffuse = param->_value;
65 
66  } else if (type == IffId("LUMI")) {
67  _flags |= F_luminosity;
68  _luminosity = param->_value;
69 
70  } else if (type == IffId("SPEC")) {
71  _flags |= F_specular;
72  _specular = param->_value;
73 
74  } else if (type == IffId("REFL")) {
75  _flags |= F_reflection;
76  _reflection = param->_value;
77 
78  } else if (type == IffId("TRAN")) {
79  _flags |= F_transparency;
80  _transparency = param->_value;
81 
82  } else if (type == IffId("GLOS")) {
83  _flags |= F_gloss;
84  _gloss = param->_value;
85 
86  } else if (type == IffId("TRNL")) {
87  _flags |= F_translucency;
88  _translucency = param->_value;
89  }
90 
91  } else if (chunk->is_of_type(LwoSurfaceSmoothingAngle::get_class_type())) {
92  const LwoSurfaceSmoothingAngle *sa = DCAST(LwoSurfaceSmoothingAngle, chunk);
93  _flags |= F_smooth_angle;
94  _smooth_angle = sa->_angle;
95 
96  } else if (chunk->is_of_type(LwoSurfaceSidedness::get_class_type())) {
97  const LwoSurfaceSidedness *sn = DCAST(LwoSurfaceSidedness, chunk);
98  _flags |= F_backface;
99  _backface = (sn->_sidedness == LwoSurfaceSidedness::S_front_and_back);
100 
101  } else if (chunk->is_of_type(LwoSurfaceBlock::get_class_type())) {
102  const LwoSurfaceBlock *lwo_block = DCAST(LwoSurfaceBlock, chunk);
103  // One of possibly several blocks in the texture that define
104  // additional fancy rendering properties.
105 
106  CLwoSurfaceBlock *block = new CLwoSurfaceBlock(_converter, lwo_block);
107 
108  // We only consider enabled "IMAP" type blocks that affect "COLR".
109  if (block->_block_type == IffId("IMAP") &&
110  block->_channel_id == IffId("COLR") &&
111  block->_enabled) {
112  // Now save the block with the lowest ordinal.
113  if (_block == (CLwoSurfaceBlock *)NULL) {
114  _block = block;
115 
116  } else if (block->_ordinal < _block->_ordinal) {
117  delete _block;
118  _block = block;
119 
120  } else {
121  delete block;
122  }
123 
124  } else {
125  delete block;
126  }
127  }
128  }
129 
130  // Now get the four-component color, based on combining the RGB and
131  // the transparency.
132  _color.set(1.0, 1.0, 1.0, 1.0);
133 
134  if ((_flags & F_rgb) != 0) {
135  _color[0] = _rgb[0];
136  _color[1] = _rgb[1];
137  _color[2] = _rgb[2];
138  }
139 
140  if ((_flags & F_transparency) != 0) {
141  _color[3] = 1.0 - _transparency;
142  }
143 
144  _diffuse_color = _color;
145 }
146 
147 ////////////////////////////////////////////////////////////////////
148 // Function: CLwoSurface::Destructor
149 // Access: Public
150 // Description:
151 ////////////////////////////////////////////////////////////////////
152 CLwoSurface::
153 ~CLwoSurface() {
154  if (_block != (CLwoSurfaceBlock *)NULL) {
155  delete _block;
156  }
157 }
158 
159 ////////////////////////////////////////////////////////////////////
160 // Function: CLwoSurface::apply_properties
161 // Access: Public
162 // Description: Applies the color, texture, etc. described by the
163 // surface to the indicated egg primitive.
164 //
165 // If the surface defines a smoothing angle,
166 // smooth_angle may be updated to reflect it if the
167 // angle is greater than that specified.
168 ////////////////////////////////////////////////////////////////////
169 void CLwoSurface::
170 apply_properties(EggPrimitive *egg_prim, vector_PT_EggVertex &egg_vertices,
171  PN_stdfloat &smooth_angle) {
172  if (!_surface->_source.empty()) {
173  // This surface is derived from another surface; apply that one
174  // first.
175  CLwoSurface *parent = _converter->get_surface(_surface->_source);
176  if (parent != (CLwoSurface *)NULL && parent != this) {
177  parent->apply_properties(egg_prim, egg_vertices, smooth_angle);
178  }
179  }
180 
181  bool has_texture = check_texture();
182  bool has_material = check_material();
183 
184  egg_prim->set_color(_diffuse_color);
185 
186  if (has_material) {
187  egg_prim->set_material(_egg_material);
188  }
189 
190  if (has_texture) {
191  egg_prim->set_texture(_egg_texture);
192 
193  // Assign UV's to the vertices.
194  generate_uvs(egg_vertices);
195  }
196 
197  if ((_flags & F_backface) != 0) {
198  egg_prim->set_bface_flag(_backface);
199  }
200 
201  if ((_flags & F_smooth_angle) != 0) {
202  smooth_angle = max(smooth_angle, _smooth_angle);
203  }
204 }
205 
206 ////////////////////////////////////////////////////////////////////
207 // Function: CLwoSurface::check_texture
208 // Access: Public
209 // Description: Checks whether the surface demands a texture or not.
210 // Returns true if so, false otherwise.
211 //
212 // If the surface demands a texture, this also sets up
213 // _egg_texture and _compute_uvs as appropriate for the
214 // texture.
215 ////////////////////////////////////////////////////////////////////
216 bool CLwoSurface::
218  if (_checked_texture) {
219  return (_egg_texture != (EggTexture *)NULL);
220  }
221  _checked_texture = true;
222  _egg_texture = (EggTexture *)NULL;
223  _map_uvs = NULL;
224 
225  if (_block == (CLwoSurfaceBlock *)NULL) {
226  // No texture. Not even a shader block.
227  return false;
228  }
229 
230  int clip_index = _block->_clip_index;
231  if (clip_index < 0) {
232  // No image file associated with the texture.
233  return false;
234  }
235 
236  CLwoClip *clip = _converter->get_clip(clip_index);
237  if (clip == (CLwoClip *)NULL) {
238  nout << "No clip image with index " << clip_index << "\n";
239  return false;
240  }
241 
242  if (!clip->is_still_image()) {
243  // Can't do anything with an animated image right now.
244  return false;
245  }
246 
247  Filename pathname = _converter->convert_model_path(clip->_filename);
248 
249  _egg_texture = new EggTexture("clip" + format_string(clip_index), pathname);
250 
251  // Do we need to generate UV's?
252  switch (_block->_projection_mode) {
253  case LwoSurfaceBlockProjection::M_planar:
254  _map_uvs = &CLwoSurface::map_planar;
255  break;
256 
257  case LwoSurfaceBlockProjection::M_cylindrical:
258  _map_uvs = &CLwoSurface::map_cylindrical;
259  break;
260 
261  case LwoSurfaceBlockProjection::M_spherical:
262  _map_uvs = &CLwoSurface::map_spherical;
263  break;
264 
265  case LwoSurfaceBlockProjection::M_cubic:
266  _map_uvs = &CLwoSurface::map_cubic;
267  break;
268 
269  case LwoSurfaceBlockProjection::M_front:
270  // Cannot generate "front" UV's, since this depends on a camera.
271  // Is it supposed to be updated in real time, like a projected
272  // texture?
273  break;
274 
275  case LwoSurfaceBlockProjection::M_uv:
276  // "uv" projection means to use the existing UV's already defined
277  // for the vertex. This case was already handled in the code that
278  // created the EggVertex pointers.
279  break;
280  };
281 
282  // Texture overrides the primitive's natural color.
283  _color[0] = 1.0;
284  _color[1] = 1.0;
285  _color[2] = 1.0;
286 
287  return true;
288 }
289 
290 ////////////////////////////////////////////////////////////////////
291 // Function: CLwoSurface::check_material
292 // Access: Public
293 // Description: Checks whether the surface demands a material or not.
294 // Returns true if so, false otherwise.
295 ////////////////////////////////////////////////////////////////////
296 bool CLwoSurface::
298  if (_checked_material) {
299  return (_egg_material != (EggMaterial *)NULL);
300  }
301  _checked_material = true;
302  _egg_material = (EggMaterial *)NULL;
303 
304  if (!_converter->_make_materials) {
305  // If we aren't making materials, then don't make a material.
306  return false;
307  }
308 
309  _egg_material = new EggMaterial(get_name());
310 
311  if ((_flags & F_diffuse) != 0) {
312  _diffuse_color.set(_color[0] * _diffuse,
313  _color[1] * _diffuse,
314  _color[2] * _diffuse,
315  _color[3]);
316  // We want to avoid setting the diffuse color on the material.
317  // We're already setting the color explicitly on the object, so
318  // there's no need to also set a diffuse color on the material,
319  // and doing so prevents nice features like set_color() and
320  // set_color_scale() from working in Panda.
321 
322  //_egg_material->set_diff(_diffuse_color);
323  }
324 
325  if ((_flags & F_luminosity) != 0) {
326  LColor luminosity(_color[0] * _luminosity,
327  _color[1] * _luminosity,
328  _color[2] * _luminosity,
329  1.0);
330  _egg_material->set_emit(luminosity);
331  }
332 
333  if ((_flags & F_specular) != 0) {
334  LColor specular(_color[0] * _specular,
335  _color[1] * _specular,
336  _color[2] * _specular,
337  1.0);
338  _egg_material->set_spec(specular);
339  }
340 
341  if ((_flags & F_gloss) != 0) {
342  _egg_material->set_shininess(_gloss * 128.0);
343  }
344 
345  return true;
346 }
347 
348 
349 ////////////////////////////////////////////////////////////////////
350 // Function: CLwoSurface::generate_uvs
351 // Access: Private
352 // Description: Computes all the UV's for the polygon's vertices,
353 // according to the _projection_mode defined in the
354 // block.
355 ////////////////////////////////////////////////////////////////////
356 void CLwoSurface::
357 generate_uvs(vector_PT_EggVertex &egg_vertices) {
358  if (_map_uvs == NULL) {
359  return;
360  }
361 
362  // To do this properly near seams and singularities (for instance,
363  // the back seam and the poles of the spherical map), we will need
364  // to know the polygon's centroid.
365  LPoint3d centroid(0.0, 0.0, 0.0);
366 
367  vector_PT_EggVertex::const_iterator vi;
368  for (vi = egg_vertices.begin(); vi != egg_vertices.end(); ++vi) {
369  EggVertex *egg_vertex = (*vi);
370  centroid += egg_vertex->get_pos3();
371  }
372 
373  centroid /= (double)egg_vertices.size();
374  centroid = centroid * _block->_inv_transform;
375 
376  // Now go back through and actually compute the UV's.
377  for (vi = egg_vertices.begin(); vi != egg_vertices.end(); ++vi) {
378  EggVertex *egg_vertex = (*vi);
379  LPoint3d pos = egg_vertex->get_pos3() * _block->_inv_transform;
380  LPoint2d uv = (this->*_map_uvs)(pos, centroid);
381  egg_vertex->set_uv(uv);
382  }
383 }
384 
385 ////////////////////////////////////////////////////////////////////
386 // Function: CLwoSurface::map_planar
387 // Access: Private
388 // Description: Computes a UV based on the given point in space,
389 // using a planar projection.
390 ////////////////////////////////////////////////////////////////////
391 LPoint2d CLwoSurface::
392 map_planar(const LPoint3d &pos, const LPoint3d &) const {
393  // A planar projection is about as easy as can be. We ignore the Y
394  // axis, and project the point into the XZ plane. Done.
395  double u = (pos[0] + 0.5);
396  double v = (pos[2] + 0.5);
397 
398  return LPoint2d(u, v);
399 }
400 
401 ////////////////////////////////////////////////////////////////////
402 // Function: CLwoSurface::map_spherical
403 // Access: Private
404 // Description: Computes a UV based on the given point in space,
405 // using a spherical projection.
406 ////////////////////////////////////////////////////////////////////
407 LPoint2d CLwoSurface::
408 map_spherical(const LPoint3d &pos, const LPoint3d &centroid) const {
409  // To compute the x position on the frame, we only need to consider
410  // the angle of the vector about the Y axis. Project the vector
411  // into the XZ plane to do this.
412 
413  LVector2d xz_orig(pos[0], pos[2]);
414  LVector2d xz = xz_orig;
415  double u_offset = 0.0;
416 
417  if (xz == LVector2d::zero()) {
418  // If we have a point on either pole, we've got problems. This
419  // point maps to the entire bottom edge of the image, so which U
420  // value should we choose? It does make a difference, especially
421  // if we have a number of polygons around the south pole that all
422  // share the common vertex.
423 
424  // We choose the U value based on the polygon's centroid.
425  xz.set(centroid[0], centroid[2]);
426 
427  } else if (xz[1] >= 0.0 && ((xz[0] < 0.0) != (centroid[0] < 0.))) {
428  // Now, if our polygon crosses the seam along the back of the
429  // sphere--that is, the point is on the back of the sphere (xz[1]
430  // >= 0.0) and not on the same side of the XZ plane as the
431  // centroid, we've got problems too. We need to add an offset to
432  // the computed U value, either 1 or -1, to keep all the vertices
433  // of the polygon on the same side of the seam.
434 
435  u_offset = (xz[0] < 0.0) ? 1.0 : -1.0;
436  }
437 
438  // The U value is based on the longitude: the angle about the Y
439  // axis.
440  double u =
441  (atan2(xz[0], -xz[1]) / (2.0 * MathNumbers::pi) + 0.5 + u_offset) * _block->_w_repeat;
442 
443  // Now rotate the vector into the YZ plane, and the V value is based
444  // on the latitude: the angle about the X axis.
445  LVector2d yz(pos[1], xz_orig.length());
446  double v =
447  (atan2(yz[0], yz[1]) / MathNumbers::pi + 0.5) * _block->_h_repeat;
448 
449  return LPoint2d(u, v);
450 }
451 
452 ////////////////////////////////////////////////////////////////////
453 // Function: CLwoSurface::map_cylindrical
454 // Access: Private
455 // Description: Computes a UV based on the given point in space,
456 // using a cylindrical projection.
457 ////////////////////////////////////////////////////////////////////
458 LPoint2d CLwoSurface::
459 map_cylindrical(const LPoint3d &pos, const LPoint3d &centroid) const {
460  // This is almost identical to the spherical projection, except for
461  // the computation of V.
462 
463  LVector2d xz(pos[0], pos[2]);
464  double u_offset = 0.0;
465 
466  if (xz == LVector2d::zero()) {
467  // Although a cylindrical mapping does not really have a
468  // singularity at the pole, it's still possible to put a point
469  // there, and we'd like to do the right thing with the polygon
470  // that shares that point. So the singularity logic remains.
471  xz.set(centroid[0], centroid[2]);
472 
473  } else if (xz[1] >= 0.0 && ((xz[0] < 0.0) != (centroid[0] < 0.))) {
474  // And cylinders do still have a seam at the back.
475  u_offset = (xz[0] < 0.0) ? 1.0 : -1.0;
476  }
477 
478  double u =
479  (atan2(xz[0], -xz[1]) / (2.0 * MathNumbers::pi) + 0.5 + u_offset) * _block->_w_repeat;
480 
481  // For a cylindrical mapping, the V value comes almost directly from
482  // Y. Easy.
483  double v = (pos[1] + 0.5);
484 
485  return LPoint2d(u, v);
486 }
487 
488 ////////////////////////////////////////////////////////////////////
489 // Function: CLwoSurface::map_cubic
490 // Access: Private
491 // Description: Computes a UV based on the given point in space,
492 // using a cubic projection.
493 ////////////////////////////////////////////////////////////////////
494 LPoint2d CLwoSurface::
495 map_cubic(const LPoint3d &pos, const LPoint3d &centroid) const {
496  // A cubic projection is a planar projection, but we eliminate the
497  // dominant axis (based on the polygon's centroid) instead of
498  // arbitrarily eliminating Y.
499 
500  double x = fabs(centroid[0]);
501  double y = fabs(centroid[1]);
502  double z = fabs(centroid[2]);
503 
504  double u, v;
505 
506  if (x > y) {
507  if (x > z) {
508  // X is dominant.
509  u = (pos[2] + 0.5);
510  v = (pos[1] + 0.5);
511  } else {
512  // Z is dominant.
513  u = (pos[0] + 0.5);
514  v = (pos[1] + 0.5);
515  }
516  } else {
517  if (y > z) {
518  // Y is dominant.
519  u = (pos[0] + 0.5);
520  v = (pos[2] + 0.5);
521  } else {
522  // Z is dominant.
523  u = (pos[0] + 0.5);
524  v = (pos[1] + 0.5);
525  }
526  }
527 
528  return LPoint2d(u, v);
529 }
A base class for any of a number of kinds of geometry primitives: polygons, point lights...
Definition: eggPrimitive.h:51
bool check_material()
Checks whether the surface demands a material or not.
bool is_still_image() const
Returns true if this clip represents a still image, as opposed to an animated image.
Definition: cLwoClip.I:36
This class is a wrapper around LwoSurfaceBlock and stores additional information useful during the co...
const string & get_name() const
Returns the name of the surface.
Definition: cLwoSurface.I:23
This is a two-component vector offset.
Definition: lvector2.h:416
Describes the shading attributes of a surface.
Definition: lwoSurface.h:28
Defines a texture map that may be applied to geometry.
Definition: eggTexture.h:33
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:63
LVertexd get_pos3() const
Valid if get_num_dimensions() returns 3 or 4.
Definition: eggVertex.I:160
void set_texture(EggTexture *texture)
Replaces the current list of textures with the indicated texture.
Definition: eggPrimitive.I:139
This is a two-component point in space.
Definition: lpoint2.h:411
CLwoClip * get_clip(int number) const
Returns a pointer to the clip with the given index number, or NULL if there is no such clip...
A texture layer or shader, part of a LwoSurface chunk.
This class is a wrapper around LwoClip and stores additional information useful during the conversion...
Definition: cLwoClip.h:32
bool check_texture()
Checks whether the surface demands a texture or not.
void set_bface_flag(bool flag)
Sets the backfacing flag of the polygon.
Definition: eggPrimitive.I:289
Records the base color of a surface, as an entry within a LwoSurface chunk.
This class is a wrapper around LwoSurface and stores additional information useful during the convers...
Definition: cLwoSurface.h:42
The basic kind of record in an EA &quot;IFF&quot; file, which the LightWave object file is based on...
Definition: iffChunk.h:32
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
void set_material(EggMaterial *material)
Applies the indicated material to the primitive.
Definition: eggPrimitive.I:241
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal...
Definition: eggVertex.h:41
Records whether polygons are frontfacing only or backfacing also.
IffId get_id() const
Returns the ID associated with this chunk.
Definition: iffChunk.I:31
static const LVector2d & zero()
Returns a zero-length vector.
Definition: lvector2.h:543
void apply_properties(EggPrimitive *egg_prim, vector_PT_EggVertex &egg_vertices, PN_stdfloat &smooth_angle)
Applies the color, texture, etc.
double length() const
Returns the length of the vector, by the Pythagorean theorem.
Definition: lvecBase3.h:2115
This class supervises the construction of an EggData structure from the data represented by the LwoHe...
Indicates the maximum angle (in radians) between adjacent polygons that should be smooth-shaded...
This is the base class for all three-component vectors and points.
Definition: lvecBase4.h:111
static int size()
Returns 3: the number of components of a LVecBase3.
Definition: lvecBase3.h:1802
This is a three-component point in space (as opposed to a three-component vector, which represents a ...
Definition: lpoint3.h:531
CLwoSurface * get_surface(const string &name) const
Returns a pointer to the surface definition with the given name, or NULL if there is no such surface...
A four-byte chunk ID appearing in an &quot;IFF&quot; file.
Definition: iffId.h:29
Records some parameter value of a surface material, as an entry within a LwoSurface chunk...
void set_uv(const LTexCoordd &texCoord)
Replaces the unnamed UV coordinate pair on the vertex with the indicated value.
Definition: eggVertex.I:239
Filename convert_model_path(const Filename &orig_filename)
Converts the indicated model filename to a relative or absolute or whatever filename, according to _path_replace.