Analog/Analogue Clock

Another amazing clock from SleepyOz — this one is old school like minecraft 🙂

Also check out the Video!

#! /usr/bin/python
"""
Draw an analogue clock into Minecraft.
Use PIL to prepare the bitmaps, then update Minecraft.

SleepyOz: tfptfp(at)gmail.com
2013-02-13
"""

import math
import mcpi.block as block
import mcpi.minecraft as minecraft
from PIL import Image # Remember to "sudo apt-get install python-imaging" to make PIL available.
from PIL import ImageDraw
import time

# Not very good colour names, because they are inverting when displayed.
colour_black = 1
colour_dark = 32
colour_medium  = 64
colour_light = 128
colour_white = 192

def clockhand(angle, length):
   """
   Calculate the end point for the given vector.
   Angle 0 is 12 o'clock, 90 is 3 o'clock.
   Based around (32,32) as origin, (0,0) in top left.
   """
   radian_angle = math.pi * angle / 180.0
   x = 32 + length * math.cos(radian_angle)
   y = 32 + length * math.sin(radian_angle)
   return [(32,32),(x,y)]

def draw_clock():
   """
   Draw an analogue clock face.
   In an attempt to improve appearance, the clock is drawn at a higher
   resolution then scaled down with anti-aliasing.
   """
   now = time.localtime()

   bitmap = Image.new("L", (65,65), color=colour_black) # Work with oversized images, and reduce before displaying.
   draw = ImageDraw.Draw(bitmap) # An object to draw into.

   # Multiple outer rings so something survives the resolution reduction later.
   draw.ellipse((1,1,64,64), outline=colour_white) # Face.
   draw.ellipse((2,2,63,63), outline=colour_white) # Face.
   draw.ellipse((3,3,62,62), outline=colour_white) # Face.
   draw.ellipse((4,4,61,61), outline=colour_white) # Face.

   # TODO: We could draw numbers or chevrons on the clock face.

   # The hands are drawn by converting the time to degrees.
   # Note that all the hands move every second, althought the change might be too small to see.
   # The hour hand only moves every minute and even that is not visible.
   draw.line(clockhand(now.tm_hour * 30 + now.tm_min / 2, 20), fill=colour_white, width=4) # Hour hand.
   draw.line(clockhand(now.tm_min * 6 + now.tm_sec / 10, 22), fill=colour_white, width=3) # Minute hand.
   draw.line(clockhand(now.tm_sec * 6, 25), fill=colour_white, width=2) # Second hand.

   bitmap.thumbnail((32,32), Image.ANTIALIAS) # Reduce resolution to suit the view distance.
   return bitmap

class buffer:
   """
   Double-buffer a voxel block for Minecraft.
   To improve performance, only changes are actually sent to Minecraft.
   """
   anchor_position = minecraft.Vec3(0,0,0)
   offscreen = None
   onscreen = None

   def __init__(self, anchor_position):
      """
      Set everything up to render the voxels into the world
      at the given position.
      """
      self.anchor_position = anchor_position
      self.onscreen = Image.new("L", (32,32), color=0)

   def render(self):
      """
      Get a picture of the clock.
      """
      self.offscreen = draw_clock()

   def flip(self, client):
      """
      Put the off-screen buffer onto the screen.
      Only send the differences.
      Remember the new screen for use during the next flip.
      """
      if self.offscreen:
         width,height = self.offscreen.size
         draw = ImageDraw.Draw(self.onscreen) # So we can remember what we did for next time.
         for x in range(0, width - 1):
            for z in range(0, height - 1):
               dot = self.offscreen.getpixel((x,z))
               if self.onscreen.getpixel((x,z)) != dot:
                  # Change detected.
                  draw.point((x,z), fill=dot)

                  # Pick display items that suit the antialiasing.
                  display = block.SNOW_BLOCK # Very white.
                  if (dot >= colour_dark+16) and (dot < colour_medium + 16):                      display = block.WOOL # Light grey.                   elif (dot >= colour_medium + 16) and (dot < colour_light + 16):                      display = block.STONE # Medium grey.                   elif (dot >= colour_light + 16):
                     display = block.OBSIDIAN # Blackish.

                  client.setBlock(self.anchor_position.x + x, self.anchor_position.y, self.anchor_position.z + z, display)

client=minecraft.Minecraft.create() # Connect to Minecraft.
place=client.player.getPos() # Start near the player.
place.y += 0 # At the level of the player's feet.
bitmapper = buffer(place)

while True:
   bitmapper.render()
   bitmapper.flip(client)
   time.sleep(.1) # Rest a while before drawing again.
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s