open_toontown_panda3d/panda/src/grutil/fisheyeMaker.cxx

409 lines
14 KiB
C++

// Filename: fisheyeMaker.cxx
// Created by: drose (3Oct05)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#include "fisheyeMaker.h"
#include "geomNode.h"
#include "geom.h"
#include "geomTristrips.h"
#include "geomVertexWriter.h"
#include "geomVertexFormat.h"
#include "geomVertexArrayFormat.h"
#include "internalName.h"
#include "luse.h"
#include "cmath.h"
#include "mathNumbers.h"
#include "graphicsStateGuardian.h"
#include "displayRegion.h"
////////////////////////////////////////////////////////////////////
// Function: FisheyeMaker::reset
// Access: Public
// Description: Resets all the parameters to their initial defaults.
////////////////////////////////////////////////////////////////////
void FisheyeMaker::
reset() {
set_fov(360.0);
_num_vertices = 1000;
_square_inscribed = false;
_square_radius = 1.0f;
set_reflection(false);
}
////////////////////////////////////////////////////////////////////
// Function: FisheyeMaker::set_fov
// Access: Public
// Description: Specifies the field of view of the fisheye
// projection. A sphere map will have a 360-degree
// field of view (and this is the default).
////////////////////////////////////////////////////////////////////
void FisheyeMaker::
set_fov(float fov) {
_fov = fov;
_half_fov_rad = deg_2_rad(_fov * 0.5f);
}
////////////////////////////////////////////////////////////////////
// Function: FisheyeMaker::generate
// Access: Public
// Description: Generates a GeomNode that renders the specified
// geometry.
////////////////////////////////////////////////////////////////////
PT(PandaNode) FisheyeMaker::
generate() {
// Get some system-imposed limits.
int max_vertices_per_array = 100;
int max_vertices_per_primitive = 10000;
bool prefers_triangle_strips = true;
/*
GraphicsStateGuardian *global_gsg = GraphicsStateGuardian::get_global_gsg();
if (global_gsg != (GraphicsStateGuardian *)NULL) {
max_vertices_per_array = global_gsg->get_max_vertices_per_array();
max_vertices_per_primitive = global_gsg->get_max_vertices_per_primitive();
prefers_triangle_strips = global_gsg->prefers_triangle_strips();
}
*/
// We will generate a rose of radius 1, with vertices approximately
// evenly distributed throughout.
// Since we will have _num_vertices filling the circle, and the area
// of a circle of radius 1 is A = pi*r^2 = pi, it follows that the
// number of vertices per square unit is (_num_vertices / pi), and
// thus the number of vertices per linear unit is the square root of
// that.
float vertices_per_unit = csqrt(_num_vertices / MathNumbers::pi_f);
float two_pi = 2.0f * MathNumbers::pi_f;
// The rose will be made up of concentric rings, originating from
// the center, to a radius of 1.0.
int num_rings = (int)floor(vertices_per_unit + 0.5f);
CPT(GeomVertexFormat) format = GeomVertexFormat::register_format
(new GeomVertexArrayFormat
(InternalName::get_vertex(), 3,
Geom::NT_float32, Geom::C_point,
InternalName::get_texcoord(), 3,
Geom::NT_float32, Geom::C_texcoord));
PT(GeomVertexData) vdata =
new GeomVertexData(get_name(), format, Geom::UH_static);
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
PT(Geom) geom = new Geom(vdata);
PT(GeomPrimitive) tristrips = new GeomTristrips(Geom::UH_static);
tristrips->set_shade_model(Geom::SM_uniform);
PT(GeomNode) geom_node = new GeomNode(get_name());
int last_ring_size = 3;
int last_ring_vertex = 0;
float last_r = 1.0f / (float)num_rings;
// Make the first triangle. We actually make a one-triangle strip,
// but that seems more sensible than making a single isolated
// triangle.
for (int vi = 0; vi < last_ring_size; ++vi) {
add_vertex(vertex, texcoord, last_r,
two_pi * (float)vi / (float)last_ring_size);
tristrips->add_vertex(vi);
}
// Actually, we need to add one more degenerate triangle to make it
// an even-length tristrip.
tristrips->add_vertex(2);
tristrips->close_primitive();
// Now make all of the rings.
for (int ri = 1; ri < num_rings; ++ri) {
float r = (float)(ri + 1) / (float)num_rings;
// The circumference of a ring of radius r is 2*pi*r.
float c = two_pi * r;
int ring_size = (int)floor(c * vertices_per_unit + 0.5f);
// Each ring must either have exactly the same number of vertices
// as the previous ring, or exactly double.
if (ring_size < last_ring_size * 2) {
// This one will be the same.
ring_size = last_ring_size;
} else {
// This one will be double.
ring_size = last_ring_size * 2;
}
if (vdata->get_num_rows() + ring_size > max_vertices_per_array) {
// Too many vertices; we need to start a new VertexData.
if (tristrips->get_num_vertices() != 0) {
geom->add_primitive(tristrips);
}
if (geom->get_num_primitives() != 0) {
if (prefers_triangle_strips) {
geom_node->add_geom(geom);
} else {
geom_node->add_geom(geom->decompose());
}
}
vdata = new GeomVertexData(get_name(), format, Geom::UH_static);
vertex = GeomVertexWriter(vdata, InternalName::get_vertex());
texcoord = GeomVertexWriter(vdata, InternalName::get_texcoord());
geom = new Geom(vdata);
tristrips = new GeomTristrips(Geom::UH_static);
tristrips->set_shade_model(Geom::SM_uniform);
// Now we need to re-make the previous ring in this VertexData.
last_ring_vertex = 0;
for (int vi = 0; vi < last_ring_size; ++vi) {
add_vertex(vertex, texcoord, last_r,
two_pi * (float)vi / (float)last_ring_size);
}
}
// Now make this ring.
int ring_vertex = vdata->get_num_rows();
for (int vi = 0; vi < ring_size; ++vi) {
add_vertex(vertex, texcoord, r,
two_pi * (float)vi / (float)ring_size);
}
// Now draw the triangle strip to connect the rings.
if (ring_size == last_ring_size) {
// Exactly the same size ring. This one is easy.
if ((ring_size + 1) * 2 > max_vertices_per_primitive) {
// Actually, we need to subdivide the ring to fit within the
// GSG's advertised limits.
int piece_size = max_vertices_per_primitive / 2 - 1;
int vi = 0;
while (vi < ring_size) {
int piece_end = min(ring_size + 1, piece_size + 1 + vi);
for (int pi = vi; pi < piece_end; ++pi) {
tristrips->add_vertex(last_ring_vertex + pi % last_ring_size);
tristrips->add_vertex(ring_vertex + pi % ring_size);
}
tristrips->close_primitive();
vi += piece_size;
}
} else {
// We can fit the entire ring.
if (tristrips->get_num_vertices() > 0 &&
tristrips->get_num_vertices() + ring_size * 2 > max_vertices_per_primitive) {
geom->add_primitive(tristrips);
tristrips = new GeomTristrips(Geom::UH_static);
tristrips->set_shade_model(Geom::SM_uniform);
}
for (int vi = 0; vi < ring_size; ++vi) {
tristrips->add_vertex(last_ring_vertex + vi);
tristrips->add_vertex(ring_vertex + vi);
}
tristrips->add_vertex(last_ring_vertex);
tristrips->add_vertex(ring_vertex);
tristrips->close_primitive();
}
} else {
// Exactly double size ring. This is harder; we can't make a
// single tristrip that goes all the way around the ring.
// Instead, we'll make an alternating series of four-triangle
// strips and two-triangle strips around the ring.
int vi = 0;
while (vi < last_ring_size) {
if (tristrips->get_num_vertices() + 10 > max_vertices_per_primitive) {
geom->add_primitive(tristrips);
tristrips = new GeomTristrips(Geom::UH_static);
tristrips->set_shade_model(Geom::SM_uniform);
}
tristrips->add_vertex(ring_vertex + (vi * 2 + 1) % ring_size);
tristrips->add_vertex(ring_vertex + (vi * 2 + 2) % ring_size);
tristrips->add_vertex(last_ring_vertex + (vi + 1) % last_ring_size);
tristrips->add_vertex(ring_vertex + (vi * 2 + 3) % ring_size);
tristrips->add_vertex(last_ring_vertex + (vi + 2) % last_ring_size);
tristrips->add_vertex(ring_vertex + (vi * 2 + 4) % ring_size);
tristrips->close_primitive();
tristrips->add_vertex(ring_vertex + (vi * 2 + 4) % ring_size);
tristrips->add_vertex(ring_vertex + (vi * 2 + 5) % ring_size);
tristrips->add_vertex(last_ring_vertex + (vi + 2) % last_ring_size);
tristrips->add_vertex(last_ring_vertex + (vi + 3) % last_ring_size);
tristrips->close_primitive();
vi += 2;
}
}
last_ring_size = ring_size;
last_ring_vertex = ring_vertex;
last_r = r;
}
if (_square_inscribed) {
// Make one more "ring", which extends out to the edges of a squre.
int ring_size = last_ring_size;
if (vdata->get_num_rows() + ring_size > max_vertices_per_array) {
// Too many vertices; we need to start a new VertexData.
if (tristrips->get_num_vertices() != 0) {
geom->add_primitive(tristrips);
}
if (geom->get_num_primitives() != 0) {
if (prefers_triangle_strips) {
geom_node->add_geom(geom);
} else {
geom_node->add_geom(geom->decompose());
}
}
vdata = new GeomVertexData(get_name(), format, Geom::UH_static);
vertex = GeomVertexWriter(vdata, InternalName::get_vertex());
texcoord = GeomVertexWriter(vdata, InternalName::get_texcoord());
geom = new Geom(vdata);
tristrips = new GeomTristrips(Geom::UH_static);
tristrips->set_shade_model(Geom::SM_uniform);
// Now we need to re-make the previous ring in this VertexData.
last_ring_vertex = 0;
for (int vi = 0; vi < last_ring_size; ++vi) {
add_vertex(vertex, texcoord, last_r,
two_pi * (float)vi / (float)last_ring_size);
}
}
// Now make this ring.
int ring_vertex = vdata->get_num_rows();
for (int vi = 0; vi < ring_size; ++vi) {
add_square_vertex(vertex, texcoord,
two_pi * (float)vi / (float)ring_size);
}
// Now draw the triangle strip to connect the rings.
if ((ring_size + 1) * 2 > max_vertices_per_primitive) {
// Actually, we need to subdivide the ring to fit within the
// GSG's advertised limits.
int piece_size = max_vertices_per_primitive / 2 - 1;
int vi = 0;
while (vi < ring_size) {
int piece_end = min(ring_size + 1, piece_size + 1 + vi);
for (int pi = vi; pi < piece_end; ++pi) {
tristrips->add_vertex(last_ring_vertex + pi % last_ring_size);
tristrips->add_vertex(ring_vertex + pi % ring_size);
}
tristrips->close_primitive();
vi += piece_size;
}
} else {
// We can fit the entire ring.
if (tristrips->get_num_vertices() > 0 &&
tristrips->get_num_vertices() + ring_size * 2 > max_vertices_per_primitive) {
geom->add_primitive(tristrips);
tristrips = new GeomTristrips(Geom::UH_static);
tristrips->set_shade_model(Geom::SM_uniform);
}
for (int vi = 0; vi < ring_size; ++vi) {
tristrips->add_vertex(last_ring_vertex + vi);
tristrips->add_vertex(ring_vertex + vi);
}
tristrips->add_vertex(last_ring_vertex);
tristrips->add_vertex(ring_vertex);
tristrips->close_primitive();
}
}
if (tristrips->get_num_vertices() != 0) {
geom->add_primitive(tristrips);
}
if (geom->get_num_primitives() != 0) {
if (prefers_triangle_strips) {
geom_node->add_geom(geom);
} else {
geom_node->add_geom(geom->decompose());
}
}
return geom_node.p();
}
////////////////////////////////////////////////////////////////////
// Function: FisheyeMaker::add_vertex
// Access: Private
// Description: Given a point defined by a radius and an angle in
// radians, compute the 2-d coordinates for the vertex
// as well as the 3-d texture coordinates, and add both
// to the VertexData.
////////////////////////////////////////////////////////////////////
void FisheyeMaker::
add_vertex(GeomVertexWriter &vertex, GeomVertexWriter &texcoord,
float r, float a) {
float sina, cosa;
csincos(a, &sina, &cosa);
// The 2-d point is just a point r units from the center of the
// circle.
LPoint3f point(r * cosa, 0.0f, r * sina);
vertex.add_data3f(point);
// The 3-d point is the same thing, bent through the third dimension
// around the surface of a sphere to the point in the back.
float b = r * _half_fov_rad;
if (b >= MathNumbers::pi_f) {
// Special case: we want to stop at the back pole, not continue
// around it.
texcoord.add_data3f(0, _reflect, 0);
} else {
float sinb, cosb;
csincos(b, &sinb, &cosb);
LPoint3f tc(sinb * cosa, cosb * _reflect, sinb * sina);
texcoord.add_data3f(tc);
}
}
////////////////////////////////////////////////////////////////////
// Function: FisheyeMaker::add_square_vertex
// Access: Private
// Description: Similar to add_vertex(), but it draws the vertex all
// the way out to the edge of the square we are
// inscribed within, and the texture coordinate is
// always the back pole.
//
// This is just for the purpose of drawing the
// inscribing square.
////////////////////////////////////////////////////////////////////
void FisheyeMaker::
add_square_vertex(GeomVertexWriter &vertex, GeomVertexWriter &texcoord,
float a) {
float sina, cosa;
csincos(a, &sina, &cosa);
// Extend the 2-d point to the edge of the square of the indicated
// size.
if (cabs(sina) > cabs(cosa)) {
float y = (sina > 0.0f) ? _square_radius : -_square_radius;
float x = y * cosa / sina;
LPoint3f point(x, 0.0f, y);
vertex.add_data3f(point);
} else {
float x = (cosa > 0.0f) ? _square_radius : -_square_radius;
float y = x * sina / cosa;
LPoint3f point(x, 0.0f, y);
vertex.add_data3f(point);
}
texcoord.add_data3f(0, _reflect, 0);
}