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