views:

378

answers:

4

Say I have a tile based system using 16x16 pixels. How would you find out what tiles are covered by a rectangle defined by floating point pixel units?

for eg,

rect(x=16.0,y=16.0, w=1.0, h=1.0) -> tile(x=1, y=1, w=1, h=1)
rect(x=16.0,y=16.0, w=16.0, h=16.0) -> tile(x=1, y=1, w=1, h=1) (still within same tile)
rect(x=24.0,y=24.0, w=8.0, y=8.0) -> (x=1,y=1,w=1,h=1) (still within same tile)
rect(x=24.0,y=24.0, w=8.1, y=8.1) -> (x=1,y=1,w=2,h=2)

The only way I can do this reliably is by using a loop. Is there a better way? Dividing by 16 gives me the wrong answer on edge cases. Here's some example code I use in python:

#!/usr/bin/env python

import math

TILE_W = 16
TILE_H = 16

def get_tile(x,y,w,h):
    t_x = int(x / TILE_W)
    t_x2 = t_x
    while t_x2*TILE_W < (x+w):
        t_x2 += 1
    t_w = t_x2-t_x

    t_y = int( y / TILE_H)
    t_y2 = t_y
    while t_y2*TILE_H < (y+h):
        t_y2 += 1
    t_h = t_y2-t_y

    return t_x,t_y,t_w,t_h

(x,y) = 16.0,16.0
(w,h) = 1.0, 1.0
assert get_tile(x,y,w,h) == (1,1,1,1)

(x,y) = 16.0,16.0
(w,h) = 15.0, 15.0
assert get_tile(x,y,w,h) == (1,1,1,1)

(x,y) = 16.0,16.0
(w,h) = 16.0, 16.0
assert get_tile(x,y,w,h) == (1,1,1,1)

(x,y) = 16.0,16.0
(w,h) = 16.1, 16.1
assert get_tile(x,y,w,h) == (1,1,2,2)

(x,y) = 24.0, 24.0
(w,h) = 1.0, 1.0
assert get_tile(x,y,w,h) == (1,1,1,1)

(x,y) = 24.0, 24.0
(w,h) = 8.0, 8.0
assert get_tile(x,y,w,h) == (1,1,1,1)

(x,y) = 24.0, 24.0
(w,h) = 8.1, 8.1
assert get_tile(x,y,w,h) == (1,1,2,2)

(x,y) = 24.0, 24.0
(w,h) = 9.0, 9.0
assert get_tile(x,y,w,h) == (1,1,2,2)
A: 

here is the one which passes your test cases, tell me if there is any edge case

TILE_W = TILE_H = 16

from math import floor, ceil

def get_tile2(x,y,w,h):
    x1 = int(x/TILE_W)
    y1 = int(y/TILE_H)
    x2 = int((x+w)/TILE_W)
    y2 = int((y+h)/TILE_H)
    if (x+w)%16 == 0: #edge case
        x2-=1
    if (y+h)%16 == 0: #edge case
        y2-=1
    tw = x2-x1 + 1
    th = y2-y1 + 1
    return x1, y1, tw, th

(x,y) = 16.0, 16.0
(w,h) = 1.0, 1.0
assert get_tile2(x,y,w,h) == (1,1,1,1)

(x,y) = 16.0, 16.0
(w,h) = 15.0, 15.0
assert get_tile2(x,y,w,h) == (1,1,1,1)

(x,y) = 16.0, 16.0
(w,h) = 16.0, 16.0
assert get_tile2(x,y,w,h) == (1,1,1,1)

(x,y) = 16.0, 16.0 
(w,h) = 16.1, 16.1
assert get_tile2(x,y,w,h) == (1,1,2,2)

I am now explicitly checking for the edge case, but be aware that floating point comparison sometime may not seem obvious and result may not be as expected.

Anurag Uniyal
This fails on the following case stated in the question:(x,y) = 16.0,16.0(w,h) = 16.1, 16.1
Matt
see the edited code
Anurag Uniyal
Isn't the ceil/floor solution more elegant for the same results? That's the solution I've always used in the past.
Matt
floor and ceiling are inadequate as the edge case occurs when a tile edge is exactly a multiple of the tile width/height.
Eric
@Eric: Can you give an example of the edge cases you are thinking about? The two solutions posted here all pass the cases stated in the questions.
Matt
A: 

You could try aligning you pixel coordinates ot integers before dividing by tile width:

xlower = int(floor(x))
xupper = int(ceil(x + w))
Ber
A: 

This could probably be condensed a bit more, but here you go.

def get_tile(x,y,w,h):

    x1 = int(x / TILE_W)
    x2 = (x + w) / TILE_W

    y1 = int(y / TILE_H)
    y2 = (x + w) / TILE_H

    if int(x2) == x2:
        x2 = int(x2 - 1)
    else:
        x2 = int(x2)

    if int(y2) == y2:
        y2 = int(y2 - 1)
    else:
        y2 = int(y2)

    tw = x2 - x1 + 1
    th = y2 - y1 + 1

    return x1, y1, tw, th
Eric
A: 

Matt's solution with bug-fixes:

from __future__ import division
import math

TILE_W = TILE_H = 16

def get_tile(x,y,w,h):
    x1 = int(math.floor(x/TILE_W))
    x2 = int(math.ceil((x + w)/TILE_W))
    y1 = int(math.floor(y/TILE_H))
    y2 = int(math.ceil((y + h)/TILE_H))
    return x1, y1, x2-x1, y2-y1
yairchu