open_toontown_panda3d/pandaapp/src/stitchbase/stitcher.cxx

478 lines
13 KiB
C++

// Filename: stitcher.cxx
// Created by: drose (09Nov99)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license. You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////
#include "stitcher.h"
#include "stitchImage.h"
#include "stitchPoint.h"
#include <rotate_to.h>
#include <compose_matrix.h>
Stitcher::MatchingPoint::
MatchingPoint(StitchPoint *p, const LPoint2d &got_uv) :
_p(p),
_got_uv(got_uv)
{
_need_uv.set(0.0, 0.0);
_diff = 0.0;
}
Stitcher::
Stitcher() {
_show_points = false;
}
Stitcher::
~Stitcher() {
}
void Stitcher::
add_image(StitchImage *image) {
image->_index = _images.size();
_images.push_back(image);
// Record all of the points in the image as well.
StitchImage::Points::const_iterator pi;
for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) {
string name = (*pi).first;
Points::iterator ppi;
ppi = _points.find(name);
StitchPoint *sp;
if (ppi != _points.end()) {
// Previously used point.
sp = (*ppi).second;
} else {
// New point.
sp = new StitchPoint(name);
_points.insert(Points::value_type(name, sp));
}
sp->_images.insert(image);
}
}
void Stitcher::
add_point(const string &name, const LVector3d &vec) {
Points::iterator ppi;
ppi = _points.find(name);
StitchPoint *sp;
if (ppi != _points.end()) {
// Previously used point.
sp = (*ppi).second;
} else {
// New point.
sp = new StitchPoint(name);
_points.insert(Points::value_type(name, sp));
}
sp->set_space(normalize(vec));
_loose_points.push_back(sp);
}
void Stitcher::
show_points(double radius, const Colord &color) {
_show_points = true;
_point_radius = radius;
_point_color = color;
}
void Stitcher::
stitch() {
if (_images.empty()) {
return;
}
// First place the reference image. All of its points are fixed
// where they are.
if (_loose_points.empty()) {
StitchImage *image = _images.front();
assert(image != NULL);
StitchImage::Points::const_iterator pi;
for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) {
string name = (*pi).first;
LPoint2d uv = (*pi).second;
Points::iterator ppi;
ppi = _points.find(name);
assert(ppi != _points.end());
StitchPoint *sp = (*ppi).second;
LVector3d space = normalize(image->extrude(uv));
sp->set_space(space);
}
_placed.push_back(image);
// Report the reference image.
nout << "\n" << *image << "\n";
_images.erase(_images.begin());
}
// Now place each of the other images relative to the already-known
// points.
double net_stitch_score = 0.0;
bool done = false;
while (!_images.empty() && !done) {
// Find the image with the greatest number of known points.
int max_score = 0;
Images::iterator best_image = _images.end();
Images::iterator ii;
for (ii = _images.begin(); ii != _images.end(); ++ii) {
int score = score_image(*ii);
if (score > max_score) {
max_score = score;
best_image = ii;
}
}
if (best_image == _images.end()) {
// Bad news. None of the images had a score greater than zero,
// so we can't stitch them in--not enough shared points.
done = true;
} else {
// Now stitch this image in and remove it from the set.
net_stitch_score += stitch_image(*best_image);
_placed.push_back(*best_image);
_images.erase(best_image);
}
}
// Any of the unplaced images with explicit hpr's get placed where
// they are.
Images::iterator ii = _images.begin();
while (ii != _images.end()) {
StitchImage *image = (*ii);
if (image->_hpr_set) {
net_stitch_score += stitch_image(image);
_placed.push_back(image);
_images.erase(ii);
} else {
++ii;
}
}
if (!_images.empty()) {
nout << "Not enough shared points; " << _images.size()
<< " images remain unstitched.\n";
}
nout << "Net score is " << net_stitch_score << "\n";
// Reorder all of the images by index number order.
sort(_placed.begin(), _placed.end(), StitchImageByIndex());
// And feather the edges between them nicely. We don't need to
// feather the first image.
if (_placed.size() > 1) {
nout << "Feathering edges\n";
Images::iterator ii;
ii = _placed.begin();
for (++ii; ii != _placed.end(); ++ii) {
feather_image(*ii);
}
}
}
int Stitcher::
score_image(StitchImage *image) {
// Give the image one point for each StitchPoint it has that has a
// known location in space.
int score = 0;
StitchImage::Points::const_iterator pi;
for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) {
string name = (*pi).first;
Points::iterator ppi;
ppi = _points.find(name);
assert(ppi != _points.end());
StitchPoint *sp = (*ppi).second;
if (sp->_space_known) {
score++;
}
}
// We must have at least two points in common to stitch an image.
if (score < 2) {
score = 0;
}
return score;
}
double Stitcher::
stitch_image(StitchImage *image) {
// First, collect all the points we have that exist somewhere in
// known space.
MatchingPoints mp;
StitchImage::Points::const_iterator pi;
for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) {
string name = (*pi).first;
LPoint2d uv = (*pi).second;
Points::iterator ppi;
ppi = _points.find(name);
assert(ppi != _points.end());
StitchPoint *sp = (*ppi).second;
if (sp->_space_known) {
mp.push_back(MatchingPoint(sp, uv));
}
}
// We need at least two points in common, or one point and an
// explicit hpr to stitch.
if (mp.size() < 2 && !image->_hpr_set) {
nout << "cannot stitch " << image->get_name() << "\n\n";
return 0.0;
}
double best_score = 0.0;
if (mp.empty()) {
// If we have no points, we can at least place it where the hpr says to.
nout << *image << "placed explicitly.\n\n";
} else {
// If we have at least one point, we can stitch something.
// Reset the image's total transform, since we'll be changing it.
image->clear_transform();
// Find the best match.
int best_i = -1;
int best_j = -1;
if (mp.size() < 2) {
// If we don't have two points, there's nothing to choose.
best_i = 0;
best_j = 0;
} else {
for (int i = 0; i < (int)mp.size(); i++) {
for (int j = 0; j < (int)mp.size(); j++) {
if (j != i) {
LMatrix3d rot;
double score = try_match(image, rot, mp, i, j);
if (score < best_score || best_i == -1) {
best_i = i;
best_j = j;
best_score = score;
}
}
}
}
}
// Now go back and actually use the best match.
LMatrix3d rot;
try_match(image, rot, mp, best_i, best_j);
image->set_transform(rot);
if (mp.size() < 2) {
nout << *image << "placed semi-explicitly.\n\n";
} else {
nout << *image << "score is " << best_score << "\n\n";
}
// Now compute the degree of success.
MatchingPoints::iterator mi;
for (mi = mp.begin(); mi != mp.end(); ++mi) {
(*mi)._need_uv = image->project((*mi)._p->_space);
(*mi)._diff = (*mi)._need_uv - (*mi)._got_uv;
}
// Now morph the image out the last few pixels so that all the
// points will match up exactly.
int x_verts = image->get_x_verts();
int y_verts = image->get_y_verts();
image->_morph.init(x_verts, y_verts);
int x, y;
for (y = 0; y < y_verts; y++) {
for (x = 0; x < x_verts; x++) {
LPoint2d p = image->get_grid_uv(x, y);
LVector2d offset(0.0, 0.0);
double net = 0.0;
MatchingPoints::const_iterator cmi;
bool done = false;
for (cmi = mp.begin(); cmi != mp.end() && !done; ++cmi) {
LVector2d v = p - (*cmi)._got_uv;
double d = pow(dot(v, v), 0.1);
if (d < 0.0001) {
// This one is dead on; stop here and never mind.
offset = (*cmi)._diff;
net = 1.0;
done = true;
} else {
double scale = 1.0 / d;
offset += (*cmi)._diff * scale;
net += scale;
}
}
offset /= net;
image->_morph._table[y][x]._p[MorphGrid::TT_out] += offset;
}
}
image->_morph.recompute();
/*
for (mi = mp.begin(); mi != mp.end(); ++mi) {
LVector3d va = normalize(LVector3d(image->extrude((*mi)._got_uv)));
LVector3d vb = (*mi)._p->_space;
nout << 1000.0 * (1.0 - dot(va, vb)) << " for " << (*mi)._p->_name
<< "\n at " << va << " vs. " << vb << "\n";
}
nout << "\n";
// Report the final results, including the morphs, to the user.
for (mi = mp.begin(); mi != mp.end(); ++mi) {
(*mi)._need_uv = image->project((*mi)._p->_space);
(*mi)._diff = (*mi)._need_uv - (*mi)._got_uv;
nout << (*mi)._p->_name
<< " "<< (*mi)._need_uv << " vs. " << (*mi)._got_uv
<< " diff is " << length((*mi)._diff * image->_uv_to_pixels)
<< " pixels\n";
}
*/
// Finally, mark all of the other points in this image as now known
// points in space.
for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) {
string name = (*pi).first;
LPoint2d uv = (*pi).second;
Points::iterator ppi;
ppi = _points.find(name);
assert(ppi != _points.end());
StitchPoint *sp = (*ppi).second;
if (!sp->_space_known) {
LVector3d space = normalize(image->extrude(uv));
sp->set_space(space);
}
}
}
return best_score;
}
void Stitcher::
feather_image(StitchImage *image) {
// Feather the edges of the image wherever it overlaps with an image
// we have laid down previously. We do this by first determining
// which morph points overlap with some other image.
int x_verts = image->get_x_verts();
int y_verts = image->get_y_verts();
if (image->_morph.is_empty()) {
image->_morph.init(x_verts, y_verts);
image->_morph.recompute();
}
int x, y;
for (y = 0; y < y_verts; y++) {
for (x = 0; x < x_verts; x++) {
LVector3d space = image->get_grid_vector(x, y);
Images::const_iterator ii;
for (ii = _placed.begin();
ii != _placed.end() &&
!image->_morph._table[y][x]._over_another;
++ii) {
StitchImage *other = (*ii);
if (other->_index < image->_index) {
LPoint2d uv = other->project(space);
if (uv[0] >= 0.0 && uv[0] <= 1.0 &&
uv[1] >= 0.0 && uv[1] <= 1.0) {
// This point is over the other image.
image->_morph._table[y][x]._over_another = true;
}
}
}
}
}
image->_morph.fill_alpha();
}
double Stitcher::
try_match(StitchImage *image, LMatrix3d &rot,
const Stitcher::MatchingPoints &mp, int zero, int one) {
// Now rotate this image relative to the other so the first pair of
// points exactly coincide.
LVector3d v0a = normalize(image->extrude(mp[zero]._got_uv));
LVector3d v0b = mp[zero]._p->_space;
rotate_to(rot, v0a, v0b);
if (zero == one) {
// Here's a special case: only one matching point. In this case,
// we roll by the explicit angle given by the user.
if (image->_hpr_set) {
rot = rot * LMatrix3d::rotate_mat(image->_hpr[2], v0b);
}
} else {
// Now (v0a * rot) == v0b. Roll about this vector till the
// second pair of points comes as close as possible to coinciding.
LVector3d v1a = normalize(image->extrude(mp[one]._got_uv));
LVector3d v1b = mp[one]._p->_space;
v1a = v1a * rot;
// We need to determine the appropriate angle to roll. This is the
// angle between the plane that contains v0 and v1a, and the plane
// that contains v0 and v1b.
LVector3d normal_a = normalize(cross(v0b, v1a));
LVector3d normal_b = normalize(cross(v0b, v1b));
double cos_theta = dot(normal_a, normal_b);
double theta = rad_2_deg(acos(cos_theta));
rot = rot * LMatrix3d::rotate_mat(-theta, v0b);
}
// Now compute the score.
double score = 0.0;
MatchingPoints::const_iterator mi;
for (mi = mp.begin(); mi != mp.end(); ++mi) {
LVector3d va = normalize(LVector3d(image->extrude((*mi)._got_uv) * rot));
LVector3d vb = (*mi)._p->_space;
score += 1.0 - dot(va, vb);
}
return 1000.0 * score;
}