#include #include #include #include #include #include #include #include #include #include #include #include #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->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); } TQPtrList Slope::moveableItems() const { TQPtrList 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->setVisible(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(TDEConfig *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(TDEConfig *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::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::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 *layout = new TQVBoxLayout(this, marginHint(), spacingHint()); KComboBox *gradient = new KComboBox(this); TQStringList items; TQString curText; for (TQMap::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, TQ_SIGNAL(activated(const TQString &)), this, TQ_SLOT(setGradient(const TQString &))); layout->addStretch(); TQCheckBox *reversed = new TQCheckBox(i18n("Reverse direction"), this); reversed->setChecked(slope->isReversed()); layout->addWidget(reversed); connect(reversed, TQ_SIGNAL(toggled(bool)), this, TQ_SLOT(setReversed(bool))); TQHBoxLayout *hlayout = new TQHBoxLayout(layout, spacingHint()); hlayout->addWidget(new TQLabel(i18n("Grade:"), this)); KDoubleNumInput *grade = new KDoubleNumInput(this); grade->setRange(0, 8, 1, true); grade->setValue(slope->curGrade()); hlayout->addWidget(grade); connect(grade, TQ_SIGNAL(valueChanged(double)), this, TQ_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()); layout->addWidget(stuck); connect(stuck, TQ_SIGNAL(toggled(bool)), this, TQ_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"