100 lines
4.1 KiB
Python
100 lines
4.1 KiB
Python
from PIL import Image, ImageFilter
|
|
import random
|
|
import math
|
|
|
|
SQUARE_SIZE = 32
|
|
SQUARES = 2**8
|
|
ITERATIONS_PER_PIXEL = 1000
|
|
COORDS = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]
|
|
|
|
|
|
def random_point_on_sphere():
|
|
theta = random.uniform(0, 2 * math.pi)
|
|
phi = math.acos(random.uniform(-1, 1))
|
|
x = math.sin(phi) * math.cos(theta)
|
|
y = math.sin(phi) * math.sin(theta)
|
|
z = math.cos(phi)
|
|
return [x, y, z]
|
|
|
|
|
|
calculated_squares = dict()
|
|
with Image.new("RGBA", (SQUARE_SIZE*SQUARES, SQUARE_SIZE), "black") as im:
|
|
px = im.load()
|
|
|
|
for square_id in range(SQUARES):
|
|
print(f"square {square_id}: ", end="")
|
|
square = square_id
|
|
|
|
# if 2 side touching tiles are obscuring, then we can assume the corner is obscuring as well
|
|
if square & (1 << 6) > 0 and square & (1 << 4) > 0:
|
|
square = square | (1 << 5)
|
|
if square & (1 << 4) > 0 and square & (1 << 2) > 0:
|
|
square = square | (1 << 3)
|
|
if square & (1 << 2) > 0 and square & (1 << 0) > 0:
|
|
square = square | (1 << 1)
|
|
if square & (1 << 0) > 0 and square & (1 << 6) > 0:
|
|
square = square | (1 << 7)
|
|
|
|
# check if we've already done the task in a rotated variation
|
|
instance_found = False
|
|
for rotation in range(4): # clockwise rotations by pi/2
|
|
rotated_square = (square >> (rotation * 2)) | ((square & ((1 << rotation*2)-1)) << 2*(4 - rotation))
|
|
if rotated_square in calculated_squares:
|
|
square_id_from = calculated_squares[rotated_square]
|
|
print(f"already calculated at {square_id_from}")
|
|
|
|
box_from = (SQUARE_SIZE*square_id_from, 0, SQUARE_SIZE*(square_id_from+1), SQUARE_SIZE)
|
|
box_to = (SQUARE_SIZE*square_id, 0, SQUARE_SIZE*(square_id+1), SQUARE_SIZE)
|
|
region = im.crop(box_from)
|
|
if rotation == 1:
|
|
region = region.transpose(Image.Transpose.ROTATE_270)
|
|
if rotation == 2:
|
|
region = region.transpose(Image.Transpose.ROTATE_180)
|
|
if rotation == 3:
|
|
region = region.transpose(Image.Transpose.ROTATE_90)
|
|
im.paste(region, box_to)
|
|
|
|
instance_found = True
|
|
break
|
|
if instance_found:
|
|
continue
|
|
|
|
# actually calculate the occlusion
|
|
obscured = [i for c, i in enumerate(COORDS) if (square >> c) & 1 == 1]
|
|
for x in range(SQUARE_SIZE):
|
|
for y in range(SQUARE_SIZE):
|
|
point_coord = ((x+0.5)/SQUARE_SIZE-0.5, (y+0.5)/SQUARE_SIZE-0.5, 0)
|
|
count = 0
|
|
for i in range(ITERATIONS_PER_PIXEL):
|
|
point = random_point_on_sphere()
|
|
point[0] += point_coord[0]
|
|
point[1] += point_coord[1]
|
|
rounded_point = (round(point[0]), round(point[1]))
|
|
if rounded_point in obscured:
|
|
count += 1
|
|
result_color = 255-255*count//ITERATIONS_PER_PIXEL
|
|
px[x+square_id*SQUARE_SIZE, y] = (result_color, result_color, result_color, 255)
|
|
calculated_squares[square] = square_id
|
|
|
|
# smooth it
|
|
box = (SQUARE_SIZE*square_id, 0, SQUARE_SIZE*(square_id+1), SQUARE_SIZE)
|
|
region = im.crop(box)
|
|
region = region.filter(filter=ImageFilter.GaussianBlur(2))
|
|
im.paste(region, box)
|
|
|
|
print(f"calculated")
|
|
|
|
print(f"total squares calculated: {len(calculated_squares)}")
|
|
|
|
# result_side_length = int(SQUARES**0.5)
|
|
# with Image.new("RGBA", (SQUARE_SIZE*result_side_length, SQUARE_SIZE*result_side_length), "black") as new_im:
|
|
# for i in range(result_side_length):
|
|
# box_from = (SQUARE_SIZE*result_side_length*i, 0, SQUARE_SIZE*result_side_length*(i+1), SQUARE_SIZE)
|
|
# box_to = (0, SQUARE_SIZE*i, SQUARE_SIZE*result_side_length, SQUARE_SIZE*(i+1))
|
|
# new_im.paste(im.crop(box_from), box_to)
|
|
# new_im.show()
|
|
# new_im.save("../images/ambient_occlusion.png")
|
|
|
|
im.show()
|
|
im.save("../images/ambient_occlusion.png")
|
|
|