You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegames/kolf/slope.cpp

586 lines
14 KiB

#include <tqbitmap.h>
#include <tqcheckbox.h>
#include <tqlabel.h>
#include <tqimage.h>
#include <tqpixmapcache.h>
#include <tqwhatsthis.h>
#include <kapplication.h>
#include <kcombobox.h>
#include <kconfig.h>
#include <knuminput.h>
#include <kpixmapeffect.h>
#include <kstandarddirs.h>
#include "slope.h"
Slope::Slope(TQRect rect, TQCanvas *canvas)
: TQCanvasRectangle(rect, canvas), type(KImageEffect::VerticalGradient), grade(4), reversed(false), color(TQColor("#327501"))
{
stuckOnGround = false;
showingInfo = false;
gradientKeys[KImageEffect::VerticalGradient] = "Vertical";
gradientKeys[KImageEffect::HorizontalGradient] = "Horizontal";
gradientKeys[KImageEffect::DiagonalGradient] = "Diagonal";
gradientKeys[KImageEffect::CrossDiagonalGradient] = "Opposite Diagonal";
gradientKeys[KImageEffect::EllipticGradient] = "Elliptic";
gradientI18nKeys[KImageEffect::VerticalGradient] = i18n("Vertical");
gradientI18nKeys[KImageEffect::HorizontalGradient] = i18n("Horizontal");
gradientI18nKeys[KImageEffect::DiagonalGradient] = i18n("Diagonal");
gradientI18nKeys[KImageEffect::CrossDiagonalGradient] = i18n("Opposite Diagonal");
gradientI18nKeys[KImageEffect::EllipticGradient] = i18n("Circular");
setZ(-50);
if (!TQPixmapCache::find("grass", grass))
{
grass.load(locate("appdata", "pics/grass.png"));
TQPixmapCache::insert("grass", grass);
}
point = new RectPoint(color.light(), this, canvas);
TQFont font(kapp->font());
font.setPixelSize(18);
text = new TQCanvasText(canvas);
text->setZ(99999.99);
text->setFont(font);
text->setColor(white);
editModeChanged(false);
hideInfo();
// this does updatePixmap
setGradient("Vertical");
}
bool Slope::terrainCollisions() const
{
// we are a terrain collision
return true;
}
void Slope::showInfo()
{
showingInfo = true;
Arrow *arrow = 0;
for (arrow = arrows.first(); arrow; arrow = arrows.next())
{
arrow->setZ(z() + .01);
arrow->setVisible(true);
}
text->tqsetVisible(true);
}
void Slope::hideInfo()
{
showingInfo = false;
Arrow *arrow = 0;
for (arrow = arrows.first(); arrow; arrow = arrows.next())
arrow->setVisible(false);
text->tqsetVisible(false);
}
void Slope::aboutToDie()
{
delete point;
clearArrows();
delete text;
}
void Slope::clearArrows()
{
Arrow *arrow = 0;
for (arrow = arrows.first(); arrow; arrow = arrows.next())
{
arrow->setVisible(false);
arrow->aboutToDie();
}
arrows.setAutoDelete(true);
arrows.clear();
arrows.setAutoDelete(false);
}
TQPtrList<TQCanvasItem> Slope::moveableItems() const
{
TQPtrList<TQCanvasItem> ret;
ret.append(point);
return ret;
}
void Slope::setGrade(double newGrade)
{
if (newGrade >= 0 && newGrade < 11)
{
grade = newGrade;
updatePixmap();
}
}
void Slope::setSize(int width, int height)
{
newSize(width, height);
}
void Slope::newSize(int width, int height)
{
if (type == KImageEffect::EllipticGradient)
{
TQCanvasRectangle::setSize(width, width);
// move point back to good spot
moveBy(0, 0);
if (game && game->isEditing())
game->updateHighlighter();
}
else
TQCanvasRectangle::setSize(width, height);
updatePixmap();
updateZ();
}
void Slope::moveBy(double dx, double dy)
{
TQCanvasRectangle::moveBy(dx, dy);
point->dontMove();
point->move(x() + width(), y() + height());
moveArrow();
updateZ();
}
void Slope::moveArrow()
{
int xavg = 0, yavg = 0;
TQPointArray r = areaPoints();
for (unsigned int i = 0; i < r.size(); ++i)
{
xavg += r[i].x();
yavg += r[i].y();
}
xavg /= r.size();
yavg /= r.size();
Arrow *arrow = 0;
for (arrow = arrows.first(); arrow; arrow = arrows.next())
arrow->move((double)xavg, (double)yavg);
if (showingInfo)
showInfo();
else
hideInfo();
text->move((double)xavg - text->boundingRect().width() / 2, (double)yavg - text->boundingRect().height() / 2);
}
void Slope::editModeChanged(bool changed)
{
point->tqsetVisible(changed);
moveBy(0, 0);
}
void Slope::updateZ(TQCanvasRectangle *vStrut)
{
const int area = (height() * width());
const int defaultz = -50;
double newZ = 0;
TQCanvasRectangle *rect = 0;
if (!stuckOnGround)
rect = vStrut? vStrut : onVStrut();
if (rect)
{
if (area > (rect->width() * rect->height()))
newZ = defaultz;
else
newZ = rect->z();
}
else
newZ = defaultz;
setZ(((double)1 / (area == 0? 1 : area)) + newZ);
}
void Slope::load(KConfig *cfg)
{
stuckOnGround = cfg->readBoolEntry("stuckOnGround", stuckOnGround);
grade = cfg->readDoubleNumEntry("grade", grade);
reversed = cfg->readBoolEntry("reversed", reversed);
// bypass updatePixmap which newSize normally does
TQCanvasRectangle::setSize(cfg->readNumEntry("width", width()), cfg->readNumEntry("height", height()));
updateZ();
TQString gradientType = cfg->readEntry("gradient", gradientKeys[type]);
setGradient(gradientType);
}
void Slope::save(KConfig *cfg)
{
cfg->writeEntry("reversed", reversed);
cfg->writeEntry("width", width());
cfg->writeEntry("height", height());
cfg->writeEntry("gradient", gradientKeys[type]);
cfg->writeEntry("grade", grade);
cfg->writeEntry("stuckOnGround", stuckOnGround);
}
void Slope::draw(TQPainter &painter)
{
painter.drawPixmap(x(), y(), pixmap);
}
TQPointArray Slope::areaPoints() const
{
switch (type)
{
case KImageEffect::CrossDiagonalGradient:
{
TQPointArray ret(3);
ret[0] = TQPoint((int)x(), (int)y());
ret[1] = TQPoint((int)x() + width(), (int)y() + height());
ret[2] = reversed? TQPoint((int)x() + width(), y()) : TQPoint((int)x(), (int)y() + height());
return ret;
}
case KImageEffect::DiagonalGradient:
{
TQPointArray ret(3);
ret[0] = TQPoint((int)x() + width(), (int)y());
ret[1] = TQPoint((int)x(), (int)y() + height());
ret[2] = !reversed? TQPoint((int)x() + width(), y() + height()) : TQPoint((int)x(), (int)y());
return ret;
}
case KImageEffect::EllipticGradient:
{
TQPointArray ret;
ret.makeEllipse((int)x(), (int)y(), width(), height());
return ret;
}
default:
return TQCanvasRectangle::areaPoints();
}
}
bool Slope::collision(Ball *ball, long int /*id*/)
{
if (grade <= 0)
return false;
double vx = ball->xVelocity();
double vy = ball->yVelocity();
double addto = 0.013 * grade;
const bool diag = type == KImageEffect::DiagonalGradient || type == KImageEffect::CrossDiagonalGradient;
const bool circle = type == KImageEffect::EllipticGradient;
double slopeAngle = 0;
if (diag)
slopeAngle = atan((double)width() / (double)height());
else if (circle)
{
const TQPoint start(x() + (int)width() / 2.0, y() + (int)height() / 2.0);
const TQPoint end((int)ball->x(), (int)ball->y());
Vector betweenVector(start, end);
const double factor = betweenVector.magnitude() / ((double)width() / 2.0);
slopeAngle = betweenVector.direction();
// this little bit by Daniel
addto *= factor * M_PI / 2;
addto = sin(addto);
}
switch (type)
{
case KImageEffect::HorizontalGradient:
reversed? vx += addto : vx -= addto;
break;
case KImageEffect::VerticalGradient:
reversed? vy += addto : vy -= addto;
break;
case KImageEffect::DiagonalGradient:
case KImageEffect::EllipticGradient:
reversed? vx += cos(slopeAngle) * addto : vx -= cos(slopeAngle) * addto;
reversed? vy += sin(slopeAngle) * addto : vy -= sin(slopeAngle) * addto;
break;
case KImageEffect::CrossDiagonalGradient:
reversed? vx -= cos(slopeAngle) * addto : vx += cos(slopeAngle) * addto;
reversed? vy += sin(slopeAngle) * addto : vy -= sin(slopeAngle) * addto;
break;
default:
break;
}
ball->setVelocity(vx, vy);
// check if the ball is at the center of a pit or mound
// or has otherwise stopped.
if (vx == 0 && vy ==0)
ball->setState(Stopped);
else
ball->setState(Rolling);
// do NOT do terrain collisions
return false;
}
void Slope::setGradient(TQString text)
{
for (TQMap<KImageEffect::GradientType, TQString>::Iterator it = gradientKeys.begin(); it != gradientKeys.end(); ++it)
{
if (it.data() == text)
{
setType(it.key());
return;
}
}
// extra forgiveness ;-) (note it's i18n keys)
for (TQMap<KImageEffect::GradientType, TQString>::Iterator it = gradientI18nKeys.begin(); it != gradientI18nKeys.end(); ++it)
{
if (it.data() == text)
{
setType(it.key());
return;
}
}
}
void Slope::setType(KImageEffect::GradientType type)
{
this->type = type;
if (type == KImageEffect::EllipticGradient)
{
// calls updatePixmap
newSize(width(), height());
}
else
updatePixmap();
}
void Slope::updatePixmap()
{
// make a gradient, make grass that's bright or dim
// merge into this->pixmap. This is drawn in draw()
// we update the arrows in this function
clearArrows();
const bool diag = type == KImageEffect::DiagonalGradient || type == KImageEffect::CrossDiagonalGradient;
const bool circle = type == KImageEffect::EllipticGradient;
const TQColor darkColor = color.dark(100 + grade * (circle? 20 : 10));
const TQColor lightColor = diag || circle? color.light(110 + (diag? 5 : .5) * grade) : color;
// hack only for circles
const bool _reversed = circle? !reversed : reversed;
TQImage gradientImage = KImageEffect::gradient(TQSize(width(), height()), _reversed? darkColor : lightColor, _reversed? lightColor : darkColor, type);
TQPixmap qpixmap(width(), height());
TQPainter p(&qpixmap);
p.drawTiledPixmap(TQRect(0, 0, width(), height()), grass);
p.end();
const double length = sqrt(width() * width() + height() * height()) / 4;
if (circle)
{
const TQColor otherLightColor = color.light(110 + 15 * grade);
const TQColor otherDarkColor = darkColor.dark(110 + 20 * grade);
TQImage otherGradientImage = KImageEffect::gradient(TQSize(width(), height()), reversed? otherDarkColor : otherLightColor, reversed? otherLightColor : otherDarkColor, KImageEffect::DiagonalGradient);
TQImage grassImage(qpixmap.convertToImage());
TQImage finalGradientImage = KImageEffect::blend(otherGradientImage, gradientImage, .60);
pixmap.convertFromImage(KImageEffect::blend(grassImage, finalGradientImage, .40));
// make arrows
double angle = 0;
for (int i = 0; i < 4; ++i)
{
angle += M_PI / 2;
Arrow *arrow = new Arrow(canvas());
arrow->setLength(length);
arrow->setAngle(angle);
arrow->setReversed(reversed);
arrow->updateSelf();
arrows.append(arrow);
}
}
else
{
Arrow *arrow = new Arrow(canvas());
float ratio = 0;
float factor = 1;
double angle = 0;
switch (type)
{
case KImageEffect::HorizontalGradient:
angle = 0;
factor = .32;
break;
case KImageEffect::VerticalGradient:
angle = M_PI / 2;
factor = .32;
break;
case KImageEffect::DiagonalGradient:
angle = atan((double)width() / (double)height());
factor = .45;
break;
case KImageEffect::CrossDiagonalGradient:
angle = atan((double)width() / (double)height());
angle = M_PI - angle;
factor = .05;
break;
default:
break;
}
float factorPart = factor * 2;
// gradePart is out of 1
float gradePart = grade / 8.0;
ratio = factorPart * gradePart;
// reverse the reversed ones
if (reversed)
ratio *= -1;
else
angle += M_PI;
KPixmap kpixmap = qpixmap;
(void) KPixmapEffect::intensity(kpixmap, ratio);
TQImage grassImage(kpixmap.convertToImage());
// okay, now we have a grass image that's
// appropriately lit, and a gradient;
// lets blend..
pixmap.convertFromImage(KImageEffect::blend(gradientImage, grassImage, .42));
arrow->setAngle(angle);
arrow->setLength(length);
arrow->updateSelf();
arrows.append(arrow);
}
text->setText(TQString::number(grade));
if (diag || circle)
{
// make cleared bitmap
TQBitmap bitmap(pixmap.width(), pixmap.height(), true);
TQPainter bpainter(&bitmap);
bpainter.setBrush(color1);
TQPointArray r = areaPoints();
// shift all the points
for (unsigned int i = 0; i < r.count(); ++i)
{
TQPoint &p = r[i];
p.setX(p.x() - x());
p.setY(p.y() - y());
}
bpainter.drawPolygon(r);
// mask is drawn
pixmap.setMask(bitmap);
}
moveArrow();
update();
}
/////////////////////////
SlopeConfig::SlopeConfig(Slope *slope, TQWidget *parent)
: Config(parent)
{
this->slope = slope;
TQVBoxLayout *tqlayout = new TQVBoxLayout(this, marginHint(), spacingHint());
KComboBox *gradient = new KComboBox(this);
TQStringList items;
TQString curText;
for (TQMap<KImageEffect::GradientType, TQString>::Iterator it = slope->gradientI18nKeys.begin(); it != slope->gradientI18nKeys.end(); ++it)
{
if (it.key() == slope->curType())
curText = it.data();
items.append(it.data());
}
gradient->insertStringList(items);
gradient->setCurrentText(curText);
tqlayout->addWidget(gradient);
connect(gradient, TQT_SIGNAL(activated(const TQString &)), this, TQT_SLOT(setGradient(const TQString &)));
tqlayout->addStretch();
TQCheckBox *reversed = new TQCheckBox(i18n("Reverse direction"), this);
reversed->setChecked(slope->isReversed());
tqlayout->addWidget(reversed);
connect(reversed, TQT_SIGNAL(toggled(bool)), this, TQT_SLOT(setReversed(bool)));
TQHBoxLayout *htqlayout = new TQHBoxLayout(tqlayout, spacingHint());
htqlayout->addWidget(new TQLabel(i18n("Grade:"), this));
KDoubleNumInput *grade = new KDoubleNumInput(this);
grade->setRange(0, 8, 1, true);
grade->setValue(slope->curGrade());
htqlayout->addWidget(grade);
connect(grade, TQT_SIGNAL(valueChanged(double)), this, TQT_SLOT(gradeChanged(double)));
TQCheckBox *stuck = new TQCheckBox(i18n("Unmovable"), this);
TQWhatsThis::add(stuck, i18n("Whether or not this slope can be moved by other objects, like floaters."));
stuck->setChecked(slope->isStuckOnGround());
tqlayout->addWidget(stuck);
connect(stuck, TQT_SIGNAL(toggled(bool)), this, TQT_SLOT(setStuckOnGround(bool)));
}
void SlopeConfig::setGradient(const TQString &text)
{
slope->setGradient(text);
changed();
}
void SlopeConfig::setReversed(bool yes)
{
slope->setReversed(yes);
changed();
}
void SlopeConfig::setStuckOnGround(bool yes)
{
slope->setStuckOnGround(yes);
changed();
}
void SlopeConfig::gradeChanged(double newgrade)
{
slope->setGrade(newgrade);
changed();
}
#include "slope.moc"