Esempio n. 1
0
 /** saturates the magnitude of a vector */
 static Geometry2d::Point saturate(Geometry2d::Point value, float max) {
     float mag = value.mag();
     if (mag > fabs(max)) {
         return value.normalized() * fabs(max);
     }
     return value;
 }
Esempio n. 2
0
//// Fixed Step Tree ////
Tree::Point* FixedStepTree::extend(Geometry2d::Point pt, Tree::Point* base) {
    // if we don't have a base point, try to find a close point
    if (!base) {
        base = nearest(pt);
        if (!base) {
            return nullptr;
        }
    }

    Geometry2d::Point delta = pt - base->pos;
    float d = delta.mag();

    Geometry2d::Point pos;
    if (d < step) {
        pos = pt;
    } else {
        pos = base->pos + delta / d * step;
    }

    // Check for obstacles.

    // moveHit is the set of obstacles that this move touches.
    // If this move touches any obstacles that the starting point didn't already
    // touch,
    // it has entered an obstacle and will be rejected.
    std::set<shared_ptr<Geometry2d::Shape>> moveHit =
        _obstacles->hitSet(Geometry2d::Segment(pos, base->pos));
    if (!moveHit.empty()) {
        // We only care if there are any items in moveHit that are not in
        // point->hit, so
        // we don't store the result of set_difference.
        try {
            set_difference(
                moveHit.begin(), moveHit.end(), base->hit.begin(),
                base->hit.end(),
                ExceptionIterator<std::shared_ptr<Geometry2d::Shape>>());
        } catch (exception& e) {
            // We hit a new obstacle
            return nullptr;
        }
    }

    // Allow this point to be added to the tree
    Point* p = new Point(pos, base);
    p->hit = std::move(moveHit);
    points.push_back(p);

    return p;
}
Esempio n. 3
0
/**
 * Generates a Cubic Bezier Path based on Albert's random Bezier Velocity Path
 * Algorithm
 */
std::vector<InterpolatedPath::Entry> RRTPlanner::generateVelocityPath(
    const std::vector<CubicBezierControlPoints>& controlPoints,
    const MotionConstraints& motionConstraints, Geometry2d::Point vi,
    Geometry2d::Point vf, int interpolations) {
    // Interpolate Through Bezier Path
    vector<Point> newPoints, newPoints1stDerivative, newPoints2ndDerivative;
    vector<float> newPointsCurvature, newPointsDistance, newPointsSpeed;

    float totalDistance = 0;
    const float maxAceleration = motionConstraints.maxAcceleration;

    for (const CubicBezierControlPoints& controlPoint : controlPoints) {
        Point p0 = controlPoint.p0;
        Point p1 = controlPoint.p1;
        Point p2 = controlPoint.p2;
        Point p3 = controlPoint.p3;
        for (int j = 0; j < interpolations; j++) {
            float t = (((float)j / (float)(interpolations)));
            Geometry2d::Point pos =
                pow(1.0 - t, 3) * p0 + 3.0 * pow(1.0 - t, 2) * t * p1 +
                3 * (1.0 - t) * pow(t, 2) * p2 + pow(t, 3) * p3;

            // Derivitive 1
            // 3 k (-(A (-1 + k t)^2) + k t (2 C - 3 C k t + D k t) + B (1 - 4 k
            // t + 3 k^2 t^2))
            Geometry2d::Point d1 = 3 * pow(1 - t, 2) * (p1 - p0) +
                                   6 * (1 - t) * t * (p2 - p1) +
                                   3 * pow(t, 2) * (p3 - p2);

            // Derivitive 2
            // https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves
            // B''(t) = 6(1-t)(P2 - 2*P1 + P0) + 6*t(P3 - 2 * P2 + P1)
            Geometry2d::Point d2 =
                6 * (1 - t) * (p2 - 2 * p1 + p0) + 6 * t * (p3 - 2 * p2 + p1);

            // https://en.wikipedia.org/wiki/Curvature#Local_expressions
            // K = |x'*y'' - y'*x''| / (x'^2 + y'^2)^(3/2)
            float curvature =
                std::abs(d1.x * d2.y - d1.y * d2.x) /
                std::pow(std::pow(d1.x, 2) + std::pow(d1.y, 2), 1.5);

            // Handle 0 velocity case
            if (isnan(curvature)) {
                curvature = 0;
            }

            assert(curvature >= 0);
            if (!newPoints.empty()) {
                float distance = pos.distTo(newPoints.back());
                totalDistance += distance;
            }
            newPointsDistance.push_back(totalDistance);

            newPoints.push_back(pos);
            newPoints1stDerivative.push_back(d1);
            newPoints2ndDerivative.push_back(d2);

            newPointsCurvature.push_back(curvature);

            // Isolated maxSpeed based on Curvature
            // Curvature = 1/Radius of Curvature
            // vmax = sqrt(acceleartion/abs(Curvature))

            float constantMaxSpeed = std::sqrt(maxAceleration / curvature);
            newPointsSpeed.push_back(constantMaxSpeed);
        }
    }
    // Get last point in Path
    CubicBezierControlPoints lastControlPoint = controlPoints.back();

    Point p0 = lastControlPoint.p0;
    Point p1 = lastControlPoint.p1;
    Point p2 = lastControlPoint.p2;
    Point p3 = lastControlPoint.p3;

    Point pos = p3;
    Point d1 = vf;
    Geometry2d::Point d2 = 6 * (1) * (p3 - 2 * p2 + p1);
    float curvature = std::abs(d1.x * d2.y - d1.y * d2.x) /
                      std::pow(std::pow(d1.x, 2) + std::pow(d1.y, 2), 1.5);

    // handle 0 velcoity case
    if (isnan(curvature)) {
        curvature = 0;
    }

    totalDistance += pos.distTo(newPoints.back());
    newPoints.push_back(pos);
    newPoints1stDerivative.push_back(vf);
    newPoints2ndDerivative.push_back(d2);
    newPointsCurvature.push_back(curvature);

    newPointsDistance.push_back(totalDistance);

    newPointsSpeed[0] = vi.mag();
    newPointsSpeed.push_back(vf.mag());

    // Velocity Profile Generation
    // Forward Smoothing
    const float size = newPoints.size();
    assert(size == newPoints.size());
    assert(size == newPoints1stDerivative.size());
    assert(size == newPoints2ndDerivative.size());
    assert(size == newPointsDistance.size());
    assert(size == newPointsSpeed.size());
    assert(size == newPointsCurvature.size());

    // Generate Velocity Profile from Interpolation of Bezier Path
    // Forward Constraints

    for (int i = 1; i < size; i++) {
        int i1 = i - 1;
        int i2 = i;
        newPointsSpeed[i2] = oneStepLimitAcceleration(
            maxAceleration, newPointsDistance[i1], newPointsSpeed[i1],
            newPointsCurvature[i1], newPointsDistance[i2], newPointsSpeed[i2],
            newPointsCurvature[i2]);
    }

    // Backwards Constraints
    for (int i = size - 2; i >= 0; i--) {
        int i1 = i + 1;
        int i2 = i;
        newPointsSpeed[i2] = oneStepLimitAcceleration(
            maxAceleration, newPointsDistance[i1], newPointsSpeed[i1],
            newPointsCurvature[i1], newPointsDistance[i2], newPointsSpeed[i2],
            newPointsCurvature[i2]);
    }

    float totalTime = 0;
    vector<InterpolatedPath::Entry> entries;

    for (int i = 0; i < size; i++) {
        float currentSpeed = newPointsSpeed[i];
        float distance = newPointsDistance[i];
        if (i != 0) {
            distance -= newPointsDistance[i - 1];
            float averageSpeed = (currentSpeed + newPointsSpeed[i - 1]) / 2.0;
            float deltaT = distance / averageSpeed;
            totalTime += deltaT;
        }

        Point point = newPoints[i];
        Point vel = newPoints1stDerivative[i].normalized();
        entries.emplace_back(MotionInstant(point, vel * currentSpeed),
                             totalTime);
    }
    return entries;
}
Esempio n. 4
0
vector<CubicBezierControlPoints> RRTPlanner::generateCubicBezierPath(
    const vector<Geometry2d::Point>& points,
    const MotionConstraints& motionConstraints, Geometry2d::Point vi,
    Geometry2d::Point vf, const boost::optional<vector<float>>& times) {
    size_t length = points.size();
    size_t curvesNum = length - 1;
    vector<double> pointsX(length);
    vector<double> pointsY(length);
    vector<double> ks(length - 1);
    vector<double> ks2(length - 1);

    for (int i = 0; i < length; i++) {
        pointsX[i] = points[i].x;
        pointsY[i] = points[i].y;
    }
    const float startSpeed = vi.mag();

    const float endSpeed = vf.mag();

    if (times) {
        assert(times->size() == points.size());
        for (int i = 0; i < curvesNum; i++) {
            ks[i] = 1.0 / (times->at(i + 1) - times->at(i));
            ks2[i] = ks[i] * ks[i];
            if (std::isnan(ks[i])) {
                debugThrow(
                    "Something went wrong. Points are too close to each other "
                    "probably");
                return vector<CubicBezierControlPoints>();
            }
        }
    } else {
        for (int i = 0; i < curvesNum; i++) {
            ks[i] = 1.0 / (getTime(points, i + 1, motionConstraints, startSpeed,
                                   endSpeed) -
                           getTime(points, i, motionConstraints, startSpeed,
                                   endSpeed));
            ks2[i] = ks[i] * ks[i];
            if (std::isnan(ks[i])) {
                debugThrow(
                    "Something went wrong. Points are too close to each other "
                    "probably");
                return vector<CubicBezierControlPoints>();
            }
        }
    }

    VectorXd solutionX =
        RRTPlanner::cubicBezierCalc(vi.x, vf.x, pointsX, ks, ks2);
    VectorXd solutionY =
        RRTPlanner::cubicBezierCalc(vi.y, vf.y, pointsY, ks, ks2);

    vector<CubicBezierControlPoints> path;

    for (int i = 0; i < curvesNum; i++) {
        Point p0 = points[i];
        Point p1 = Geometry2d::Point(solutionX(i * 2), solutionY(i * 2));
        Point p2 =
            Geometry2d::Point(solutionX(i * 2 + 1), solutionY(i * 2 + 1));
        Point p3 = points[i + 1];
        path.emplace_back(p0, p1, p2, p3);
    }
    return path;
}
Esempio n. 5
0
WorldBall::WorldBall(RJ::Time calcTime, std::list<KalmanBall> kalmanBalls) 
    : isValid(true), time(calcTime){
        
    Geometry2d::Point posAvg = Geometry2d::Point(0, 0);
    Geometry2d::Point velAvg = Geometry2d::Point(0, 0);
    double totalPosWeight = 0;
    double totalVelWeight = 0;

    // Below 1 would invert the ratio of scaling
    // Above 2 would just be super noisy
    if (*ball_merger_power < 1 || *ball_merger_power > 2) {

        std::cout
             << "WARN: ball_merger_power should be between 1 and 2"
             << std::endl;
    }

    if (kalmanBalls.size() == 0) {
        std::cout
             << "ERROR: Zero balls are given to the WorldBall constructor"
             << std::endl;

        isValid = false;
        pos = posAvg;
        vel = velAvg;
        posCov = 0;
        velCov = 0;

        return;
    }

    for (KalmanBall& ball : kalmanBalls) {
        // Get the covariance of everything
        // AKA how well we can predict the next measurement
        Geometry2d::Point posCov = ball.getPosCov();
        Geometry2d::Point velCov = ball.getVelCov();

        // Std dev of each state
        // Lower std dev gives better idea of true values
        Geometry2d::Point posStdDev;
        Geometry2d::Point velStdDev;
        posStdDev.x() = std::sqrt(posCov.x());
        posStdDev.y() = std::sqrt(posCov.y());
        velStdDev.x() = std::sqrt(velCov.x());
        velStdDev.y() = std::sqrt(velCov.y());

        // Inversely proportional to how much the filter has been updated
        double filterUncertantity = 1.0 / ball.getHealth();

        // How good of pos/vel estimation in total
        // (This is less efficient than just doing the sqrt(x_cov + y_cov),
        //  but it's a little more clear math-wise)
        double posUncertantity = posStdDev.mag();
        double velUncertantity = velStdDev.mag();

        // Weight better estimates higher
        double filterPosWeight = std::pow(posUncertantity * filterUncertantity,
                                          -*ball_merger_power);

        double filterVelWeight = std::pow(velUncertantity * filterUncertantity,
                                          -*ball_merger_power);

        posAvg += filterPosWeight * ball.getPos();
        velAvg += filterVelWeight * ball.getVel();

        totalPosWeight += filterPosWeight;
        totalVelWeight += filterVelWeight;
    }

    posAvg /= totalPosWeight;
    velAvg /= totalVelWeight;

    pos = posAvg;
    vel = velAvg;
    posCov = totalPosWeight / kalmanBalls.size();
    velCov = totalVelWeight / kalmanBalls.size();
    ballComponents = kalmanBalls;
}
void FieldView::drawTeamSpace(QPainter& p) {
    // Get the latest LogFrame
    const LogFrame* frame = _history->at(0).get();

    if (showTeamNames) {
        // Draw Team Names
        QFont savedFont = p.font();
        QFont fontstyle = p.font();
        fontstyle.setPointSize(20);
        p.setFont(fontstyle);
        p.setPen(bluePen);
        drawText(p, QPointF(0, 4.75), QString(frame->team_name_blue().c_str()),
                 true);  // Blue
        p.setPen(yellowPen);
        drawText(p, QPointF(0, 1.75),
                 QString(frame->team_name_yellow().c_str()),
                 true);  // Yellow
        p.setFont(savedFont);
    }

    // Block off half the field
    if (!frame->use_our_half()) {
        const float FX = Field_Dimensions::Current_Dimensions.FloorWidth() / 2;
        const float FY1 = -Field_Dimensions::Current_Dimensions.Border();
        const float FY2 = Field_Dimensions::Current_Dimensions.Length() / 2;
        p.fillRect(QRectF(QPointF(-FX, FY1), QPointF(FX, FY2)),
                   QColor(0, 0, 0, 128));
    }
    if (!frame->use_opponent_half()) {
        const float FX = Field_Dimensions::Current_Dimensions.FloorWidth() / 2;
        const float FY1 = Field_Dimensions::Current_Dimensions.Length() / 2;
        const float FY2 = Field_Dimensions::Current_Dimensions.Length() +
                          Field_Dimensions::Current_Dimensions.Border();
        p.fillRect(QRectF(QPointF(-FX, FY1), QPointF(FX, FY2)),
                   QColor(0, 0, 0, 128));
    }

    if (showCoords) {
        drawCoords(p);
    }

    // History
    p.setBrush(Qt::NoBrush);
    QPainterPath ballTrail;
    for (unsigned int i = 0; i < 200 && i < _history->size(); ++i) {
        const LogFrame* oldFrame = _history->at(i).get();
        if (oldFrame && oldFrame->has_ball()) {
            QPointF pos = qpointf(oldFrame->ball().pos());

            if (i == 0)
                ballTrail.moveTo(pos);
            else
                ballTrail.lineTo(pos);
        }
    }
    QPen ballTrailPen(ballColor, 0.03);
    ballTrailPen.setCapStyle(Qt::RoundCap);
    p.setPen(ballTrailPen);
    p.drawPath(ballTrail);

    // Debug lines
    for (const DebugPath& path : frame->debug_paths()) {
        if (path.layer() < 0 || layerVisible(path.layer())) {
            tempPen.setColor(qcolor(path.color()));
            p.setPen(tempPen);
            std::vector<QPointF> pts;
            for (int i = 0; i < path.points_size(); ++i) {
                pts.push_back(qpointf(path.points(i)));
            }
            p.drawPolyline(pts.data(), pts.size());
        }
    }

    for (const DebugRobotPath& path : frame->debug_robot_paths()) {
        if (path.layer() < 0 || layerVisible(path.layer())) {
            for (int i = 0; i < path.points_size() - 1; ++i) {
                const DebugRobotPath::DebugRobotPathPoint& from =
                    path.points(i);
                const DebugRobotPath::DebugRobotPathPoint& to =
                    path.points(i + 1);

                Geometry2d::Point avgVel =
                    (Geometry2d::Point(path.points(i).vel()) +
                     Geometry2d::Point(path.points(i + 1).vel())) /
                    2;
                float pcntMaxSpd =
                    avgVel.mag() / MotionConstraints::defaultMaxSpeed();
                QColor mixedColor(std::min((int)(255 * pcntMaxSpd), 255), 0,
                                  std::min((int)(255 * (1 - pcntMaxSpd)), 255));
                QPen pen(mixedColor);
                pen.setCapStyle(Qt::RoundCap);
                pen.setWidthF(0.03);
                p.setPen(pen);

                const Geometry2d::Point fromPos = Geometry2d::Point(from.pos());
                const Geometry2d::Point toPos = Geometry2d::Point(to.pos());
                p.drawLine(fromPos.toQPointF(), toPos.toQPointF());
            }
        }
    }

    // Debug circles
    for (const DebugCircle& c : frame->debug_circles()) {
        if (c.layer() < 0 || layerVisible(c.layer())) {
            tempPen.setColor(c.color());
            p.setPen(tempPen);
            p.drawEllipse(qpointf(c.center()), c.radius(), c.radius());
        }
    }

    // Debug arcs
    for (const DebugArc& a : frame->debug_arcs()) {
        if (a.layer() < 0 || layerVisible(a.layer())) {
            tempPen.setColor(a.color());
            p.setPen(tempPen);

            auto c = a.center();
            auto t1 = a.start();
            auto t2 = a.end();
            auto R = a.radius();

            QRectF rect;
            rect.setX(-R + c.x());
            rect.setY(-R + c.y());
            rect.setWidth(R * 2);
            rect.setHeight(R * 2);

            t1 *= -(180 / M_PI) * 16;
            t2 *= -(180 / M_PI) * 16;

            p.drawArc(rect, t1, t2 - t1);
        }
    }

    // Debug text
    for (const DebugText& text : frame->debug_texts()) {
        if (text.layer() < 0 || layerVisible(text.layer())) {
            tempPen.setColor(text.color());
            p.setPen(tempPen);
            drawText(p, qpointf(text.pos()),
                     QString::fromStdString(text.text()), text.center());
        }
    }

    // Debug polygons
    p.setPen(Qt::NoPen);
    for (const DebugPath& path : frame->debug_polygons()) {
        if (path.layer() < 0 || layerVisible(path.layer())) {
            if (path.points_size() < 3) {
                fprintf(stderr, "Ignoring DebugPolygon with %d points\n",
                        path.points_size());
                continue;
            }

            QColor color = qcolor(path.color());
            color.setAlpha(64);
            p.setBrush(color);
            std::vector<QPointF> pts;
            for (int i = 0; i < path.points_size(); ++i) {
                pts.push_back(qpointf(path.points(i)));
            }
            p.drawConvexPolygon(pts.data(), pts.size());
        }
    }
    p.setBrush(Qt::NoBrush);

    // maps robots to their comet trails, so we can draw a path of where each
    // robot has been over the past X frames the pair used as a key is of the
    // form (team, robot_id).  Blue team = 1, yellow = 2. we only draw trails
    // for robots that exist in the current frame
    map<pair<int, int>, QPainterPath> cometTrails;

    /// populate @cometTrails with the past locations of each robot
    int pastLocationCount = 40;  // number of past locations to show
    for (int i = 0; i < pastLocationCount + 1 && i < _history->size(); i++) {
        const LogFrame* oldFrame = _history->at(i).get();
        if (oldFrame) {
            for (const LogFrame::Robot& r : oldFrame->self()) {
                pair<int, int> key(1, r.shell());
                if (cometTrails.find(key) != cometTrails.end() || i == 0) {
                    QPointF pt = qpointf(r.pos());
                    if (i == 0)
                        cometTrails[key].moveTo(pt);
                    else
                        cometTrails[key].lineTo(pt);
                }
            }

            for (const LogFrame::Robot& r : oldFrame->self()) {
                pair<int, int> key(2, r.shell());
                if (cometTrails.find(key) != cometTrails.end() || i == 0) {
                    QPointF pt = qpointf(r.pos());
                    if (i == 0)
                        cometTrails[key].moveTo(pt);
                    else
                        cometTrails[key].lineTo(pt);
                }
            }
        }
    }

    // draw robot comet trails
    const float cometTrailPenSize = 0.07;
    for (auto& kv : cometTrails) {
        QColor color = kv.first.first == 1 ? Qt::blue : Qt::yellow;
        QPen pen(color, cometTrailPenSize);
        pen.setCapStyle(Qt::RoundCap);
        p.setPen(pen);
        p.drawPath(kv.second);
    }

    // Text positioning vectors
    QPointF rtX = qpointf(Geometry2d::Point(0, 1).rotated(-_rotate * 90));
    QPointF rtY = qpointf(Geometry2d::Point(-1, 0).rotated(-_rotate * 90));

    // Opponent robots
    for (const LogFrame::Robot& r : frame->opp()) {
        drawRobot(p, !frame->blue_team(), r.shell(), qpointf(r.pos()),
                  r.angle(), r.ball_sense_status() == HasBall);
    }

    // Our robots
    int manualID = frame->manual_id();
    for (const LogFrame::Robot& r : frame->self()) {
        QPointF center = qpointf(r.pos());

        bool faulty = false;
        if (r.has_ball_sense_status() && (r.ball_sense_status() == Dazzled ||
                                          r.ball_sense_status() == Failed)) {
            faulty = true;
        }
        if (r.has_kicker_works() && !r.kicker_works()) {
            // 			faulty = true;
        }
        for (int i = 0; i < r.motor_status().size(); ++i) {
            if (r.motor_status(i) != Good) {
                faulty = true;
            }
        }
        if (r.has_battery_voltage() && r.battery_voltage() <= 14.3f) {
            faulty = true;
        }

        drawRobot(p, frame->blue_team(), r.shell(), center, r.angle(),
                  r.ball_sense_status() == HasBall, faulty);

        // Highlight the manually controlled robot
        if (manualID == r.shell()) {
            p.setPen(greenPen);
            const float r = Robot_Radius + .05;
            p.drawEllipse(center, r, r);
        }

        // Robot text
        QPointF textPos = center - rtX * 0.2 - rtY * (Robot_Radius + 0.1);
        for (const DebugText& text : r.text()) {
            if (text.layer() < 0 || layerVisible(text.layer())) {
                tempPen.setColor(text.color());
                p.setPen(tempPen);
                drawText(p, textPos, QString::fromStdString(text.text()),
                         false);
                textPos -= rtY * 0.1;
            }
        }
    }

    // Current ball position and velocity
    if (frame->has_ball()) {
        QPointF pos = qpointf(frame->ball().pos());
        QPointF vel = qpointf(frame->ball().vel());

        p.setPen(ballPen);
        p.setBrush(ballColor);
        p.drawEllipse(QRectF(-Ball_Radius + pos.x(), -Ball_Radius + pos.y(),
                             Ball_Diameter, Ball_Diameter));

        if (!vel.isNull()) {
            p.drawLine(pos, QPointF(pos.x() + vel.x(), pos.y() + vel.y()));
        }
    }
}
Esempio n. 7
0
bool Gameplay::Behaviors::GoalDefender::run()
{
	if (!assigned() || !allVisible())
	{
		return false;
	}

	Geometry2d::Point ballFuture = ball().pos + ball().vel*0.1;
	const float radius = .7;

	//goal line, for intersection detection
	Geometry2d::Segment goalLine(Geometry2d::Point(-Constants::Field::GoalWidth / 2.0f, 0),
								  Geometry2d::Point(Constants::Field::GoalWidth / 2.0f, 0));

	// Update the target window
	_winEval->exclude.clear();
	for (Robot *r :  _robots)
	{
		_winEval->exclude.push_back(r->pos());
	}

	_winEval->run(ballFuture, goalLine);

	Window* best = 0;
	float bestDist = 0;

	// finds the closest segment to the ball
	for (Window* window :  _winEval->windows)
	{
		Geometry2d::Segment seg(window->segment.center(), ball().pos);
		float newDist = seg.distTo(Behavior::robot()->pos());

		if (seg.length() > Constants::Robot::Radius && (!best || newDist < bestDist))
		{
			best = window;
			bestDist = newDist;
		}
	}

	if (!best)
	{
	    //FIXME - What if there are no windows?
	    return true;
	}

	Geometry2d::Circle arc(Geometry2d::Point(), radius);
	Geometry2d::Line ballTravel(ball().pos, ballFuture);
	Geometry2d::Point dest[2];
	bool ballTravelIntersects = ballTravel.intersects(arc, &dest[0], &dest[1]);
	Geometry2d::Point blockPoint = (dest[0].y > 0 ? dest[0] : dest[1]);
	bool movingTowardsGoal = ballFuture.mag() < ball().pos.mag();

	if(ballTravelIntersects && blockPoint.y > 0 && movingTowardsGoal)
	{
		// If ball is traveling towards the goal, block it.
		state()->drawText("YY", blockPoint, Qt::magenta);
	}
	else
	{	// Stand in the largest open window
		Geometry2d::Line bestWindowLine(best->segment.center(), Geometry2d::Point(0,0));
		Geometry2d::Point blockPoint = (best->segment.center().normalized()) * radius;
		state()->drawText("XX", blockPoint, Qt::white);
	}



	/*
	_winEval->exclude.push_back(Behavior::robot()->pos());

	//exclude robots that arn't the defender
	//_winEval->run(ball().pos, goalLine);

	for (Defender *f :  otherDefenders)
	{
		if (f->robot())
		{
			_winEval->exclude.push_back(f->robot()->pos());
		}
	}

	_winEval->run(ballFuture, goalLine);

	Window* best = 0;

	Behavior* goalie = _gameplay->goalie();

	bool needTask = false;

	//pick biggest window on appropriate side
	if (goalie && goalie->robot())
	{
		for (Window* window :  _winEval->windows)
		{
			if (_side == Left)
			{
				if (!best || window->segment.center().x < goalie->robot()->pos().x)
				{
					best = window;
				}
			}
			else if (_side == Right)
			{
				if (!best || window->segment.center().x > goalie->robot()->pos().x)
				{
					best = window;
				}
			}
		}
	}
	else
	{
		//if no side parameter...stay in the middle
		float bestDist = 0;
		for (Window* window :  _winEval->windows)
		{
			Geometry2d::Segment seg(window->segment.center(), ball().pos);
			float newDist = seg.distTo(Behavior::robot()->pos());

			if (!best || newDist < bestDist)
			{
				best = window;
				bestDist = newDist;
			}
		}
	}

	if (best)
	{
		Geometry2d::Segment shootLine(ball().pos, ball().pos + ball().vel.normalized() * 7.0);

		Geometry2d::Segment& winSeg = best->segment;

		if (ball().vel.magsq() > 0.03 && winSeg.intersects(shootLine))
		{
			robot()->move(shootLine.nearestPoint(Behavior::robot()->pos()));
			robot()->faceNone();
		}
		else
		{
			const float winSize = winSeg.length();

			if (winSize < Constants::Ball::Radius)
			{
				needTask = true;
			}
			else
			{
				const float radius = .7;

				Geometry2d::Circle arc(Geometry2d::Point(), radius);

				Geometry2d::Line shot(winSeg.center(), ballFuture);
				Geometry2d::Point dest[2];

				bool intersected = shot.intersects(arc, &dest[0], &dest[1]);

				if (intersected)
				{
					if (dest[0].y > 0)
					{
						Behavior::robot()->move(dest[0]);
					}
					else
					{
						Behavior::robot()->move(dest[1]);
					}
					Behavior::robot()->face(ballFuture);
				}
				else
				{
					needTask = true;
				}
			}
		}
	}
	else
	{
		needTask = true;
	}*/

	return false;
}