scale object by distance to camera, on multiple cameras

I am trying to create an effect where an object will scale up as it gets farther away from the camera, up to a a maximum scale at a specific distance.
For example:
distance = 0, scale = 1
distance = 5, scale = 0.5
distance = 10, scale = 2

Normally this would be easy, except I have multiple cameras looking at the same objects and rendering to buffers, so I can’t just set the scale on a NodePath.

Any ideas how I could achieve this?

Right now the best way looks to be to code a new RenderEffect that will apply scale.

Have multiple models and make them visible from only one camera each.

That would work, but it is not ideal because it is still doing calculations even if the objects are out of view. Also it would be nice to have a generic solution that can be reused.

I managed to add a new RenderEffect based on the BillboardEffect that I named ResizeEffect. It works… kind of. The problem is if the node has a transform on it, it is scaling from the wrong spot, as in the scale is affecting the position. I’m not really familiar with how the TransformState works, maybe someone can enlighten me?

This is what I have for the function:

//     Function: ResizeEffect::compute_resize
//       Access: Private
//  Description: Computes the resize operation given the parent's
//               net transform and the camera transform.
//               The result is applied to node_transform, which is
//               modified in-place.
void ResizeEffect::
compute_resize(CPT(TransformState) &node_transform, 
               const TransformState *net_transform, 
               const TransformState *camera_transform) const {
  PN_stdfloat distance = (net_transform->get_pos() - camera_transform->get_pos()).length_squared();
  PN_stdfloat scale;
  if (distance >= _distance_max * _distance_max) {
    scale = _scale_max;
  } else if (distance <= _distance_min * _distance_min) {
    scale = _scale_min;
  } else {
    scale = _scale_min + (_scale_max - _scale_min) * ((sqrt(distance) - _distance_min) / (_distance_max - _distance_min));
  cout << "dist=" << sqrt(distance) << " scale=" << scale << "\n";
  node_transform = node_transform->set_scale(LVecBase3(scale, scale, scale));

A TransformState simply stores a 4x4 matrix (LMatrix4). All of the other accessors and setters are just sugar on top of that underlying matrix.

Scaling a matrix means multiplying the matrix times a scale matrix: orig * LMatrix4.scaleMat(sx). Scaling a matrix around a point other than (0, 0, 0) means multiplying three matrices: orig * LMatrix4.translateMat(x, y, z) * LMatrix4.scaleMat(sx) * LMatrix4.translateMat(-x, -y, -z).

I might have the order of operations reversed. :wink:


Ah, that makes sense. I thought it was some sort of list of all the transforms applied. I figured out that my code actually works, it was just that the object scaling up as it got further away created an illusion that the center of the object was moving.

It was enough to do

node_transform = node_transform->set_scale(node_transform->get_scale() * scale);

which seems to be doing all the complex steps behind the scenes.