summaryrefslogtreecommitdiffstats
path: root/kolf/slope.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kolf/slope.cpp')
-rw-r--r--kolf/slope.cpp585
1 files changed, 585 insertions, 0 deletions
diff --git a/kolf/slope.cpp b/kolf/slope.cpp
new file mode 100644
index 00000000..e2becea1
--- /dev/null
+++ b/kolf/slope.cpp
@@ -0,0 +1,585 @@
+#include <qbitmap.h>
+#include <qcheckbox.h>
+#include <qlabel.h>
+#include <qimage.h>
+#include <qpixmapcache.h>
+#include <qwhatsthis.h>
+
+#include <kapplication.h>
+#include <kcombobox.h>
+#include <kconfig.h>
+#include <knuminput.h>
+#include <kpixmapeffect.h>
+#include <kstandarddirs.h>
+
+#include "slope.h"
+
+Slope::Slope(QRect rect, QCanvas *canvas)
+ : QCanvasRectangle(rect, canvas), type(KImageEffect::VerticalGradient), grade(4), reversed(false), color(QColor("#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 (!QPixmapCache::find("grass", grass))
+ {
+ grass.load(locate("appdata", "pics/grass.png"));
+ QPixmapCache::insert("grass", grass);
+ }
+
+ point = new RectPoint(color.light(), this, canvas);
+
+ QFont font(kapp->font());
+ font.setPixelSize(18);
+ text = new QCanvasText(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->setVisible(true);
+}
+
+void Slope::hideInfo()
+{
+ showingInfo = false;
+ Arrow *arrow = 0;
+ for (arrow = arrows.first(); arrow; arrow = arrows.next())
+ arrow->setVisible(false);
+ text->setVisible(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);
+}
+
+QPtrList<QCanvasItem> Slope::moveableItems() const
+{
+ QPtrList<QCanvasItem> 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)
+ {
+ QCanvasRectangle::setSize(width, width);
+ // move point back to good spot
+ moveBy(0, 0);
+
+ if (game && game->isEditing())
+ game->updateHighlighter();
+ }
+ else
+ QCanvasRectangle::setSize(width, height);
+
+ updatePixmap();
+ updateZ();
+}
+
+void Slope::moveBy(double dx, double dy)
+{
+ QCanvasRectangle::moveBy(dx, dy);
+
+ point->dontMove();
+ point->move(x() + width(), y() + height());
+
+ moveArrow();
+ updateZ();
+}
+
+void Slope::moveArrow()
+{
+ int xavg = 0, yavg = 0;
+ QPointArray 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->setVisible(changed);
+ moveBy(0, 0);
+}
+
+void Slope::updateZ(QCanvasRectangle *vStrut)
+{
+ const int area = (height() * width());
+ const int defaultz = -50;
+
+ double newZ = 0;
+
+ QCanvasRectangle *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
+ QCanvasRectangle::setSize(cfg->readNumEntry("width", width()), cfg->readNumEntry("height", height()));
+ updateZ();
+
+ QString 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(QPainter &painter)
+{
+ painter.drawPixmap(x(), y(), pixmap);
+}
+
+QPointArray Slope::areaPoints() const
+{
+ switch (type)
+ {
+ case KImageEffect::CrossDiagonalGradient:
+ {
+ QPointArray ret(3);
+ ret[0] = QPoint((int)x(), (int)y());
+ ret[1] = QPoint((int)x() + width(), (int)y() + height());
+ ret[2] = reversed? QPoint((int)x() + width(), y()) : QPoint((int)x(), (int)y() + height());
+
+ return ret;
+ }
+
+ case KImageEffect::DiagonalGradient:
+ {
+ QPointArray ret(3);
+ ret[0] = QPoint((int)x() + width(), (int)y());
+ ret[1] = QPoint((int)x(), (int)y() + height());
+ ret[2] = !reversed? QPoint((int)x() + width(), y() + height()) : QPoint((int)x(), (int)y());
+
+ return ret;
+ }
+
+ case KImageEffect::EllipticGradient:
+ {
+ QPointArray ret;
+ ret.makeEllipse((int)x(), (int)y(), width(), height());
+ return ret;
+ }
+
+ default:
+ return QCanvasRectangle::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 QPoint start(x() + (int)width() / 2.0, y() + (int)height() / 2.0);
+ const QPoint 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(QString text)
+{
+ for (QMap<KImageEffect::GradientType, QString>::Iterator it = gradientKeys.begin(); it != gradientKeys.end(); ++it)
+ {
+ if (it.data() == text)
+ {
+ setType(it.key());
+ return;
+ }
+ }
+
+ // extra forgiveness ;-) (note it's i18n keys)
+ for (QMap<KImageEffect::GradientType, QString>::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 QColor darkColor = color.dark(100 + grade * (circle? 20 : 10));
+ const QColor lightColor = diag || circle? color.light(110 + (diag? 5 : .5) * grade) : color;
+ // hack only for circles
+ const bool _reversed = circle? !reversed : reversed;
+ QImage gradientImage = KImageEffect::gradient(QSize(width(), height()), _reversed? darkColor : lightColor, _reversed? lightColor : darkColor, type);
+
+ QPixmap qpixmap(width(), height());
+ QPainter p(&qpixmap);
+ p.drawTiledPixmap(QRect(0, 0, width(), height()), grass);
+ p.end();
+
+ const double length = sqrt(width() * width() + height() * height()) / 4;
+
+ if (circle)
+ {
+ const QColor otherLightColor = color.light(110 + 15 * grade);
+ const QColor otherDarkColor = darkColor.dark(110 + 20 * grade);
+ QImage otherGradientImage = KImageEffect::gradient(QSize(width(), height()), reversed? otherDarkColor : otherLightColor, reversed? otherLightColor : otherDarkColor, KImageEffect::DiagonalGradient);
+
+ QImage grassImage(qpixmap.convertToImage());
+
+ QImage 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);
+
+ QImage 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(QString::number(grade));
+
+ if (diag || circle)
+ {
+ // make cleared bitmap
+ QBitmap bitmap(pixmap.width(), pixmap.height(), true);
+ QPainter bpainter(&bitmap);
+ bpainter.setBrush(color1);
+ QPointArray r = areaPoints();
+
+ // shift all the points
+ for (unsigned int i = 0; i < r.count(); ++i)
+ {
+ QPoint &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, QWidget *parent)
+ : Config(parent)
+{
+ this->slope = slope;
+ QVBoxLayout *layout = new QVBoxLayout(this, marginHint(), spacingHint());
+ KComboBox *gradient = new KComboBox(this);
+ QStringList items;
+ QString curText;
+ for (QMap<KImageEffect::GradientType, QString>::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);
+ layout->addWidget(gradient);
+ connect(gradient, SIGNAL(activated(const QString &)), this, SLOT(setGradient(const QString &)));
+
+ layout->addStretch();
+
+ QCheckBox *reversed = new QCheckBox(i18n("Reverse direction"), this);
+ reversed->setChecked(slope->isReversed());
+ layout->addWidget(reversed);
+ connect(reversed, SIGNAL(toggled(bool)), this, SLOT(setReversed(bool)));
+
+ QHBoxLayout *hlayout = new QHBoxLayout(layout, spacingHint());
+ hlayout->addWidget(new QLabel(i18n("Grade:"), this));
+ KDoubleNumInput *grade = new KDoubleNumInput(this);
+ grade->setRange(0, 8, 1, true);
+ grade->setValue(slope->curGrade());
+ hlayout->addWidget(grade);
+ connect(grade, SIGNAL(valueChanged(double)), this, SLOT(gradeChanged(double)));
+
+ QCheckBox *stuck = new QCheckBox(i18n("Unmovable"), this);
+ QWhatsThis::add(stuck, i18n("Whether or not this slope can be moved by other objects, like floaters."));
+ stuck->setChecked(slope->isStuckOnGround());
+ layout->addWidget(stuck);
+ connect(stuck, SIGNAL(toggled(bool)), this, SLOT(setStuckOnGround(bool)));
+}
+
+void SlopeConfig::setGradient(const QString &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"