Jump to content
View in the app

A better way to browse. Learn more.

OSBot :: 2007 OSRS Botting

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Draw the bounding box of an area

Featured Replies

Hi all,

 

I'm trying to paint the bounding box of an Area. I tried Area.getPolgyon() and g.draw(polygon), though I suspect as Area is an extension of the polygon class, it doesnt quite work to just draw the polygon from the superclass.

 

Instead, I used Area.getPositions, and then iterate through each position and draw bounding boxes on those - however this is very noisy, as it draws a mesh (box around every tile obviously) rather than just the outer edges of the Area.

 

the code I'm using for the per-tile drawing is:

 

for (Position p : gs.getCombatArea().getPositions()) {
	if (p.isVisible(bot))
		g.drawPolygon(p.getPolygon(bot));
}

 

I suspect I have two options, but I would appreciate a push in the right direction.

 

Any help would be greatly appreciated.

18 minutes ago, tel0seh said:

Hi all,

 

I'm trying to paint the bounding box of an Area. I tried Area.getPolgyon() and g.draw(polygon), though I suspect as Area is an extension of the polygon class, it doesnt quite work to just draw the polygon from the superclass.

 

Instead, I used Area.getPositions, and then iterate through each position and draw bounding boxes on those - however this is very noisy, as it draws a mesh (box around every tile obviously) rather than just the outer edges of the Area.

 

the code I'm using for the per-tile drawing is:

 


for (Position p : gs.getCombatArea().getPositions()) {
	if (p.isVisible(bot))
		g.drawPolygon(p.getPolygon(bot));
}

 

I suspect I have two options, but I would appreciate a push in the right direction.

 

Any help would be greatly appreciated.

 

I'm confused as to what you are saying...

g.drawPolygon(area.getPolygon());

Should work no?

What is the result when you try it?

Edited by Explv

  • Author
54 minutes ago, Explv said:

 

I'm confused as to what you are saying...

g.drawPolygon(area.getPolygon());

Should work no?

What is the result when you try it?

Nothing is drawn on the screen. I'm not sure why.

The docs state that Area.getPolygon() returns "the java.awt.Ploygon that this class is based off" which i assume is not quite the same as a polygon that represents the visual placement of that area on screen - which is why you need to pass a Bot object for 

g.drawPolygon(p.getPolygon(bot))

which does seem to work with a Point object.

There is no similar implementation for Area for example area.getPolygon(bot), only Area.getPolygon() exists which takes no bot parameter.

2 hours ago, tel0seh said:

Nothing is drawn on the screen. I'm not sure why.

The docs state that Area.getPolygon() returns "the java.awt.Ploygon that this class is based off" which i assume is not quite the same as a polygon that represents the visual placement of that area on screen - which is why you need to pass a Bot object for 


g.drawPolygon(p.getPolygon(bot))

which does seem to work with a Point object.

There is no similar implementation for Area for example area.getPolygon(bot), only Area.getPolygon() exists which takes no bot parameter.

Maybe you're right, i'm not sure.

I guess you could maybe try combining the polygons of each position and then drawing the end result? Something like:

(NOTE UNTESTED)

area.getPositions()
    .stream()
    .map(pos -> pos.getPolygon(getBot()))
    .map(java.awt.geom.Area::new)
    .reduce((area1, area2) -> {
        area1.add(area2);
        return area1;
    })
    .ifPresent(g::draw);

 

What the code does:

  • Gets the List<Position> in the area using getPositions()
  • Constructs a Stream<Position> by calling stream()
  • Maps each position to it's Polygon by calling getPolygon, now you have a Stream<Polygon>
  • Maps each polygon to an Area instance by calling new Area(polygon), I use the Area::new method reference to make this shorter, now you have a Stream<Area>
  • Reduces the Stream<Area> into a single Area, all the Areas are added together using the add(Area) method
  • If an Area is present, we draw it on screen using graphics.draw(Shape)

Edited by Explv

  • Author
23 minutes ago, Explv said:

Maybe you're right, i'm not sure.

I guess you could maybe try combining the polygons of each position and then drawing the end result? Something like:

(NOTE UNTESTED)


area.getPositions()
    .stream()
    .map(pos -> pos.getPolygon(getBot()))
    .map(java.awt.geom.Area::new)
    .reduce((area1, area2) -> {
        area1.add(area2);
        return area1;
    })
    .ifPresent(g::draw);

 

So this is really close but doesnt quite work! - as i dont understand your snippet I can't add the fix myself. It does draw the bounding boxes of the outline by reducing the number of points, but also has some artifact lines left over, it looks like this.

http://imgur.com/a/VzFl2

 

Can you either explain your code a little more so i can figure it out if the fix isn't immediately obvious, or provide a fix?  Ideally the former, because I'd like to understand it more than i want it to "just work"

 

Honestly I really appreciate your help.

2 hours ago, tel0seh said:

So this is really close but doesnt quite work! - as i dont understand your snippet I can't add the fix myself. It does draw the bounding boxes of the outline by reducing the number of points, but also has some artifact lines left over, it looks like this.

http://imgur.com/a/VzFl2

 

Can you either explain your code a little more so i can figure it out if the fix isn't immediately obvious, or provide a fix?  Ideally the former, because I'd like to understand it more than i want it to "just work"

 

Honestly I really appreciate your help.

 

Apologies, I have added an explanation.

I am unable to test & fix myself atm because I am at work, but I can take a look later this evening.

Perhaps you could try drawing the bounds (outline) of the final Shape, instead of the Shape itself?

Note: this would only work if your Area is a rectangle

area.getPositions()
    .stream()
    .map(pos -> pos.getPolygon(getBot()))
    .map(java.awt.geom.Area::new)
    .reduce((area1, area2) -> {
        area1.add(area2);
        return area1;
    })
    .map(shape -> shape.getBounds2D())
    .ifPresent(g::draw);

 

What the code does:

  • Gets the List<Position> in the area using getPositions()
  • Constructs a Stream<Position> by calling stream()
  • Maps each position to it's Polygon by calling getPolygon, now you have a Stream<Polygon>
  • Maps each polygon to an Area instance by calling new Area(polygon), I use the Area::new method reference to make this shorter, now you have a Stream<Area>
  • Reduces the Stream<Area> into a single Area, all the Areas are added together using the add(Area) method
  • Map the combined Area Shape to it's bounds (Rectangle outline)
  • If the final Rectangle is present, we draw it on screen using graphics.draw(Shape)

Edited by Explv

  • Author
3 hours ago, Explv said:

 

Apologies, I have added an explanation.

I am unable to test & fix myself atm because I am at work, but I can take a look later this evening.

Perhaps you could try drawing the bounds (outline) of the final Shape, instead of the Shape itself?

Note: this would only work if your Area is a rectangle


area.getPositions()
    .stream()
    .map(pos -> pos.getPolygon(getBot()))
    .map(java.awt.geom.Area::new)
    .reduce((area1, area2) -> {
        area1.add(area2);
        return area1;
    })
    .map(shape -> shape.getBounds2D())
    .ifPresent(g::draw);

 

What the code does:

  • Gets the List<Position> in the area using getPositions()
  • Constructs a Stream<Position> by calling stream()
  • Maps each position to it's Polygon by calling getPolygon, now you have a Stream<Polygon>
  • Maps each polygon to an Area instance by calling new Area(polygon), I use the Area::new method reference to make this shorter, now you have a Stream<Area>
  • Reduces the Stream<Area> into a single Area, all the Areas are added together using the add(Area) method
  • Map the combined Area Shape to it's bounds (Rectangle outline)
  • If the final Rectangle is present, we draw it on screen using graphics.draw(Shape)

Closer again! but not quite there - it draws a rectangle of the outer perimeter of the area, but does not account for the camera/tiles. The images linked below show what I mean since it's hard to explain.

 

http://imgur.com/a/FNbOk

The original solution was closer, mapping it to a shape/to its bounds seems to have "removed" the camera/bot context from it, so it just gets drawn as a square for the appropriate size on the screen and isnt drawn on top of the tiles like the initial suggestion.

 

I suspect the final solution to the problem lies somewhere in the middle - as you were able to call g.draw(java.awt.geom.Area area)  but  org.osbot.rs07.api.map.Area doesnt directly translate. Can we somehow convert the osbot Area implementation into the java.awt.Area (which i suspect is its superclass) - there's something being lost in translation there.

Could you provide some links to Stream, and Map so i can get those figured out and maybe come up with a solution? Possibly a tweak in the reduce function on the original suggestion?

 

I'll try a few things in the meantime, with casting etc.

 

Thanks again!

Edited by tel0seh

  • Author

Another little update - After a bit of research, there's a "convex hull" algorithm that will return a set of points that encloses all other points in a set. I suspect if we break down all the positions->polygons->points and merge them into a list, then we run convex hull on the point sets, create a polygon of the points, and then draw it it should work.

I'm currently working on this but if you have a fix before i get there even better.

Edited by tel0seh

1 hour ago, tel0seh said:

Another little update - After a bit of research, there's a "convex hull" algorithm that will return a set of points that encloses all other points in a set. I suspect if we break down all the positions->polygons->points and merge them into a list, then we run convex hull on the point sets, create a polygon of the points, and then draw it it should work.

I'm currently working on this but if you have a fix before i get there even better.

Yes the convex hull algorithm would work. There is probably an easier overall solution, but I cba to think of one right now.

Using https://github.com/bkiers/GrahamScan

List<Position> positions = area.getPositions();
List<Point> points = new ArrayList<>();

for (Position pos : positions) {
    Polygon polygon = pos.getPolygon(getBot());
    for (int i = 0; i < polygon.xpoints.length; i ++) {
        points.add(new Point(polygon.xpoints[i], polygon.ypoints[i]));
    }
}

List<Point> convexHull = GrahamScan.getConvexHull(points);

Polygon areaShape = new Polygon();
for (Point point : convexHull) {
    areaShape.addPoint(point.x, point.y);
}

g.draw(areaShape);

 

  • Author
34 minutes ago, Eliot said:

Nothing useful to offer, this used to take like two lines w/ OSBot 1. :feels:

annoying, but I've got it working in two ways, one of which should be easier.

 

The ConvexHull method above works, however i also got it working with:

https://osbot.org/api/org/osbot/rs07/api/util/GraphicUtilities.html

Using GraphicUtilities.getBoundingBox(short [][])

 

to get the short array, I used this (which to be honest was a guessing game, I only vaguely understand what i was doing with map and stream)

getCombatArea().getPositions().stream().map(pos->pos.getVertices()).collect(Collectors.toList());

which gives you List<short [][]> - then flatten it into a single short[][] -> pass it to getBoundingbox, draw the polygon.

 

 

im using ConvexHull now - its more code but seems cleaner.

 

Edited by tel0seh

Here is a solution for tracing an area (now with more enum):

Spoiler

import java.awt.Graphics2D;
import java.awt.Point;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.osbot.rs07.api.map.Area;
import org.osbot.rs07.api.map.Position;
import org.osbot.rs07.script.MethodProvider;

public class DrawingUtils {
	
	/**
	 * Traces the given area
	 * @param mp   The MethodProvider, either your Script or getBot().getMethods()
	 * @param a    Area to trace
	 * @param g    Graphics2D from onPaint
	 */
	public static void drawArea(MethodProvider mp, Area a, Graphics2D g) {
		// Get the vertices for the position, and convert into Points.
		Map<Position, List<Point>> positions = a.getPositions()
			.stream()
			.collect(
				Collectors.toMap(
					p -> p,
					p -> Arrays.asList(p.getVertices(mp.getBot()))
							.stream()
							.map(v -> new Point(v[0], v[1]))
							.collect(Collectors.toList())
				)
			);
		// Go through each position, drawing the faces that are the boundary
		for (Position p : positions.keySet()) {
			List<Point> points = positions.get(p);
			
			for (Face t : Face.values()) {
				if (!positions.containsKey(t.translate(p)) && t.isValid(points))
					// Draw this position
					t.draw(g, points);
			}
		}
	}
	
	/**
	 * Represents a face of a tile, and contains the proper translation info and which vertice indexes to use.
	 */
	private enum Face {
		NORTH( 0,  1, 3, 0),
		EAST ( 1,  0, 0, 1),
		SOUTH( 0, -1, 1, 2),
		WEST (-1,  0, 2, 3);
		
		final int x, y;
		final int[] indexes;

		private Face(int x, int y, int index1, int index2) {
			this.x = x;
			this.y = y;
			this.indexes = new int[] { index1, index2 };
		}
		
		public Position translate(Position p) {
			return p.translate(x, y);
		}
		
		public boolean isValid(List<Point> points) {
			return isValid(points.get(indexes[0])) && isValid(points.get(indexes[1]));
		}

		public void draw(Graphics2D g, List<Point> points) {
			Point a = points.get(indexes[0]),
					b = points.get(indexes[1]);
			g.drawLine(a.x, a.y, b.x, b.y);
		}

		/**
		 * Helper function to see if a Point is onscreen
		 * @param p
		 * @return
		 */
		private boolean isValid(Point p) {
			return p.x >= 0 && p.y >= 0;
		}
	}

}

Usage is as follows (in a Script):

	@Override
	public void onPaint(Graphics2D g) {
		DrawingUtils.drawArea(this, area, g);
	}

Results using a poly area:

NphVrkR.png

Edited by Lemons

  • Author
15 hours ago, Lemons said:

Here is a solution for tracing an area (now with more enum):

  Reveal hidden contents


import java.awt.Graphics2D;
import java.awt.Point;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.osbot.rs07.api.map.Area;
import org.osbot.rs07.api.map.Position;
import org.osbot.rs07.script.MethodProvider;

public class DrawingUtils {
	
	/**
	 * Traces the given area
	 * @param mp   The MethodProvider, either your Script or getBot().getMethods()
	 * @param a    Area to trace
	 * @param g    Graphics2D from onPaint
	 */
	public static void drawArea(MethodProvider mp, Area a, Graphics2D g) {
		// Get the vertices for the position, and convert into Points.
		Map<Position, List<Point>> positions = a.getPositions()
			.stream()
			.collect(
				Collectors.toMap(
					p -> p,
					p -> Arrays.asList(p.getVertices(mp.getBot()))
							.stream()
							.map(v -> new Point(v[0], v[1]))
							.collect(Collectors.toList())
				)
			);
		// Go through each position, drawing the faces that are the boundary
		for (Position p : positions.keySet()) {
			List<Point> points = positions.get(p);
			
			for (Face t : Face.values()) {
				if (!positions.containsKey(t.translate(p)) && t.isValid(points))
					// Draw this position
					t.draw(g, points);
			}
		}
	}
	
	/**
	 * Represents a face of a tile, and contains the proper translation info and which vertice indexes to use.
	 */
	private enum Face {
		NORTH( 0,  1, 3, 0),
		EAST ( 1,  0, 0, 1),
		SOUTH( 0, -1, 1, 2),
		WEST (-1,  0, 2, 3);
		
		final int x, y;
		final int[] indexes;

		private Face(int x, int y, int index1, int index2) {
			this.x = x;
			this.y = y;
			this.indexes = new int[] { index1, index2 };
		}
		
		public Position translate(Position p) {
			return p.translate(x, y);
		}
		
		public boolean isValid(List<Point> points) {
			return isValid(points.get(indexes[0])) && isValid(points.get(indexes[1]));
		}

		public void draw(Graphics2D g, List<Point> points) {
			Point a = points.get(indexes[0]),
					b = points.get(indexes[1]);
			g.drawLine(a.x, a.y, b.x, b.y);
		}

		/**
		 * Helper function to see if a Point is onscreen
		 * @param p
		 * @return
		 */
		private boolean isValid(Point p) {
			return p.x >= 0 && p.y >= 0;
		}
	}

}

Usage is as follows (in a Script):


	@Override
	public void onPaint(Graphics2D g) {
		DrawingUtils.drawArea(this, area, g);
	}

Results using a poly area:

NphVrkR.png

Thanks, also quite an elegant solution. Not sure whether this would be computationally more or less expensive than the Convex Hull.

Edited by tel0seh

Recently Browsing 0

  • No registered users viewing this page.

Account

Navigation

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.