ale/d3/pt.h
2022-07-30 14:46:04 -03:00

546 lines
12 KiB
C++

// Copyright 2005 David Hilvert <dhilvert@auricle.dyndns.org>,
// <dhilvert@ugcs.caltech.edu>
/* This file is part of the Anti-Lamenessing Engine.
The Anti-Lamenessing Engine is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
The Anti-Lamenessing Engine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the Anti-Lamenessing Engine; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* d3/et.h: Represent 3D->2D projective transformations.
*/
#ifndef __pt_h__
#define __pt_h__
#include "space.h"
#include "et.h"
/*
* Structure to describe a 3D->2D projective transformation. 3D information is
* preserved by adding a depth element to the result.
*
* The following coordinate systems are considered:
*
* P: projective
* C: local cartesian
* W: world
*/
struct pt {
private:
d2::transformation t;
et euclidean;
ale_pos _view_angle;
ale_pos scale_factor;
mutable ale_pos diag_per_depth;
public:
/*
* Constructor
*/
pt() : t(d2::transformation::eu_identity()) {
_view_angle = M_PI / 4;
scale_factor = 1;
diag_per_depth = 0;
}
pt(d2::transformation t, et e, ale_pos va, ale_pos sf = 1) : t(t) {
this->t = t;
euclidean = e;
_view_angle = va;
scale_factor = sf;
diag_per_depth = 0;
}
/*
* Output function
*/
void debug_output() {
t.debug_output();
euclidean.debug_output();
fprintf(stderr, "[pt.do va=%f sf=%f dpd=%f\n]",
(double) _view_angle, (double) scale_factor, (double) diag_per_depth);
}
/*
* Get euclidean transformation reference.
*/
et &e() {
return euclidean;
}
/*
* Modify scale factor
*/
void scale(ale_pos sf) {
scale_factor = sf;
}
/*
* Modify or get view angle
*/
void view_angle(ale_pos va) {
diag_per_depth = 0;
_view_angle = va;
}
ale_pos view_angle() {
return _view_angle;
}
/*
* Get the 2D scale factor
*/
ale_pos scale_2d() const {
return t.scale();
}
/*
* Transform W to C.
*/
point wc(point p) const {
return euclidean(p);
}
/*
* Transform C to P for given width and height.
*/
point cp_generic(point p, ale_pos w, ale_pos h) const {
/*
* Divide x and y by negative z
*/
p[0] /= -p[2];
p[1] /= -p[2];
/*
* Scale x and y
*/
ale_pos scaling_factor = (ale_pos) sqrt(w*w + h*h)
/ (ale_pos) (2 * tan(_view_angle / 2));
p[0] *= scaling_factor;
p[1] *= scaling_factor;
/*
* Add an offset so that the upper-left corner is the origin.
*/
p[0] += h / 2;
p[1] += w / 2;
return p;
}
/*
* Transform point p.
*/
struct point wp_generic(struct point p, ale_pos w, ale_pos h) const {
return cp_generic(wc(p), w, h);
}
/*
* Width and height
*/
ale_pos scaled_width() const {
return t.scaled_width() * scale_factor;
}
ale_pos scaled_height() const {
return t.scaled_height() * scale_factor;
}
int scaled_in_bounds(point p) const {
return (p[0] >= 0 && p[0] <= scaled_height() - 1
&& p[1] >= 0 && p[1] <= scaled_width() - 1);
}
int unscaled_in_bounds(point p) const {
return (p[0] >= 0 && p[0] <= unscaled_height() - 1
&& p[1] >= 0 && p[1] <= unscaled_width() - 1);
}
ale_pos unscaled_width() const {
return t.unscaled_width() * scale_factor;
}
ale_pos unscaled_height() const {
return t.unscaled_height() * scale_factor;
}
/*
* Scaled transforms
*/
point cp_scaled(point p) const {
return cp_generic(p, scaled_width(), scaled_height());
}
point wp_scaled(point p) const {
return wp_generic(p, scaled_width(), scaled_height());
}
/*
* Unscaled transforms
*/
point cp_unscaled(point p) const {
return cp_generic(p, unscaled_width(), unscaled_height());
}
point wp_unscaled(point p) const {
return wp_generic(p, unscaled_width(), unscaled_height());
}
/*
* Transform P to C.
*/
point pc_generic(point p, ale_pos w, ale_pos h) const {
/*
* Subtract offset
*/
p[0] -= h / 2;
p[1] -= w / 2;
/*
* Scale x and y
*/
ale_pos scaling_factor = (ale_pos) sqrt(w*w + h*h)
/ (ale_pos) (2 * tan(_view_angle / 2));
p[0] /= scaling_factor;
p[1] /= scaling_factor;
/*
* Multiply x and y by negative z
*/
p[0] *= -p[2];
p[1] *= -p[2];
return p;
}
/*
* Transform C to W
*/
point cw(point p) const {
return euclidean.inverse_transform(p);
}
/*
* Transform P to W
*/
point pw_generic(point p, ale_pos w, ale_pos h) const {
return cw(pc_generic(p, w, h));
}
/*
* Inverse transforms for scaled points.
*/
point pc_scaled(point p) const {
return pc_generic(p, scaled_width(), scaled_height());
}
point pw_scaled(point p) const {
return pw_generic(p, scaled_width(), scaled_height());
}
/*
* Inverse transforms for unscaled points.
*/
point pc_unscaled(point p) const {
return pc_generic(p, unscaled_width(), unscaled_height());
}
point pw_unscaled(point p) const {
return pw_generic(p, unscaled_width(), unscaled_height());
}
/*
* Density calculation
*/
ale_pos c_density_scaled(point p) const {
ale_pos one_density = 1 / (pc_scaled(point(0, 0, -1)).lengthto(pc_scaled(point(0, 1, -1)))
* pc_scaled(point(0, 0, -1)).lengthto(pc_scaled(point(1, 0, -1))));
return one_density / (p[2] * p[2]);
}
ale_pos w_density_scaled(point p) const {
return c_density_scaled(wc(p));
}
ale_pos w_density_scaled_max(point w0, point w1, point w2) {
point c0 = wc(w0);
point c1 = wc(w1);
point c2 = wc(w2);
/*
* Select the point closest to the camera.
*/
if (c0[2] > c1[2] && c0[2] > c2[2])
return c_density_scaled(c0);
else if (c1[2] > c2[2])
return c_density_scaled(c1);
else
return c_density_scaled(c2);
}
private:
void calculate_diag_per_depth() const {
if (diag_per_depth != 0)
return;
ale_pos w = unscaled_width();
ale_pos h = unscaled_height();
diag_per_depth = sqrt(2) * (2 * tan(_view_angle / 2)) / sqrt(w*w + h*h);
}
public:
/*
* Get a trilinear coordinate for a given depth.
*/
ale_pos trilinear_coordinate(ale_pos depth, ale_pos diagonal) {
calculate_diag_per_depth();
return log(diagonal / (fabs(depth) * diag_per_depth)) / log(2);
}
/*
* Get a trilinear coordinate for a given position in the world and
* a given 2D diagonal distance.
*/
ale_pos trilinear_coordinate(point w, ale_pos diagonal) {
return trilinear_coordinate(wc(w)[2], diagonal);
}
/*
* Get a trilinear coordinate for a given subspace.
*/
ale_pos trilinear_coordinate(const space::traverse &st) {
point min = st.get_min();
point max = st.get_max();
point avg = (min + max) / (ale_pos) 2;
ale_pos diagonal = min.lengthto(max) * (ale_pos) (sqrt(2) / sqrt(3));
return trilinear_coordinate(avg, diagonal);
}
/*
* Get a diagonal distance for a given position in the world
* and a given trilinear coordinate.
*/
ale_pos diagonal_distance(point w, ale_pos coordinate) const {
calculate_diag_per_depth();
ale_pos depth = fabs(wc(w)[2]);
ale_pos diagonal = pow(2, coordinate) * depth * diag_per_depth;
return diagonal;
}
/*
* Get the 3D diagonal for a given depth and trilinear coordinate.
*/
ale_pos diagonal_distance_3d(ale_pos depth, ale_pos coordinate) const {
calculate_diag_per_depth();
return pow(2, coordinate) * fabs(depth) * diag_per_depth * (ale_pos) (sqrt(3) / sqrt(2));
}
/*
* Get the 1D distance for a given depth and trilinear coordinate.
*/
ale_pos distance_1d(ale_pos depth, ale_pos coordinate) const {
calculate_diag_per_depth();
return pow(2, coordinate) * fabs(depth) * diag_per_depth / (ale_pos) (sqrt(2));
}
ale_pos distance_1d(point iw, ale_pos coordinate) const {
if (wc(iw)[2] >= 0)
return point::undefined()[0];
return distance_1d(-wc(iw)[2], coordinate);
}
/*
* Check for inclusion of a point in the bounding box of projected
* vertices. This function returns non-zero when a point is included,
* when one of the vertices is infinite or undefined, or when a vertex
* is behind the point of projection.
*
* WBB is assumed to contain {volume_min, volume_max}.
*/
int check_inclusion(const point *wbb, const d2::point &pc_min, const d2::point &pc_max, int scaled) const {
assert(pc_min[0] <= pc_max[0]);
assert(pc_min[1] <= pc_max[1]);
int test[2][2] = {{0, 0}, {0, 0}};
for (int x = 0; x < 2; x++)
for (int y = 0; y < 2; y++)
for (int z = 0; z < 2; z++) {
point p = scaled ? wp_scaled(point(wbb[x][0], wbb[y][1], wbb[z][2]))
: wp_unscaled(point(wbb[x][0], wbb[y][1], wbb[z][2]));
if (!p.finite())
return 1;
if (p[2] > 0)
return 1;
for (int d = 0; d < 2; d++) {
if (p[d] <= pc_max[d])
test[d][0] = 1;
if (p[d] >= pc_min[d])
test[d][1] = 1;
}
}
for (int d = 0; d < 2; d++)
for (int c = 0; c < 2; c++)
if (test[d][c] == 0)
return 0;
return 1;
}
int check_inclusion_scaled(const point *wbb, d2::point pc_min, d2::point pc_max) const {
return check_inclusion(wbb, pc_min, pc_max, 1);
}
int check_inclusion_scaled(const space::traverse &st, d2::point pc_min, d2::point pc_max) const {
return check_inclusion_scaled(st.get_bounds(), pc_min, pc_max);
}
int check_inclusion_scaled(const space::traverse &st, d2::point pc) {
return check_inclusion_scaled(st, pc, pc);
}
/*
* Get bounding box for projection of a subspace.
*/
void get_view_local_bb(point volume_min, point volume_max, point bb[2], int scaled) const {
point min = point::posinf();
point max = point::neginf();
point wbb[2] = { volume_min, volume_max };
for (int x = 0; x < 2; x++)
for (int y = 0; y < 2; y++)
for (int z = 0; z < 2; z++) {
point p = scaled ? wp_scaled(point(wbb[x][0], wbb[y][1], wbb[z][2]))
: wp_unscaled(point(wbb[x][0], wbb[y][1], wbb[z][2]));
for (int d = 0; d < 3; d++) {
if (p[d] < min[d])
min[d] = p[d];
if (p[d] > max[d])
max[d] = p[d];
}
}
/*
* Clip bounding box to image extents.
*/
if (min[0] < 0)
min[0] = 0;
if (min[1] < 0)
min[1] = 0;
if (max[0] > scaled_height() - 1)
max[0] = scaled_height() - 1;
if (max[1] > scaled_width() - 1)
max[1] = scaled_width() - 1;
bb[0] = min;
bb[1] = max;
}
void get_view_local_bb_unscaled(point volume_min, point volume_max, point bb[2]) const {
get_view_local_bb(volume_min, volume_max, bb, 0);
}
void get_view_local_bb_scaled(point volume_min, point volume_max, point bb[2]) const {
get_view_local_bb(volume_min, volume_max, bb, 1);
}
void get_view_local_bb_scaled(const space::traverse &st, point bb[2]) const {
get_view_local_bb_scaled(st.get_min(), st.get_max(), bb);
}
void get_view_local_bb_unscaled(const space::traverse &t, point bb[2]) const {
get_view_local_bb_unscaled(t.get_min(), t.get_max(), bb);
}
/*
* Get the in-bounds centroid for a subspace, if one exists.
*/
point centroid(point volume_min, point volume_max) const {
point bb[2];
get_view_local_bb_unscaled(volume_min, volume_max, bb);
point min = bb[0];
point max = bb[1];
for (int d = 0; d < 2; d++)
if (min[d] > max[d])
return point::undefined();
if (min[2] >= 0)
return point::undefined();
if (max[2] > 0)
max[2] = 0;
return (max + min) / 2;
}
point centroid(const space::traverse &t) {
return centroid(t.get_min(), t.get_max());
}
/*
* Get the local space origin in world space.
*/
point origin() {
return cw(point(0, 0, 0));
}
};
#endif