Files
2020-07-29 12:59:12 +02:00

311 lines
11 KiB
Python

from .window import Window
from . import colors
from pygame.locals import *
import math
import numpy as np
class Plane:
def __init__(self,theme={},view=None, zoom_sensibility=0.1, move_sensibility=0.05):
"""Create a plane using optionals theme and view."""
self.createTheme(theme)
self.createView(view)
self.zoom_sensibility = zoom_sensibility
self.move_sensibility = move_sensibility
def createTheme(self,theme={}):
"""Initializes the position and the colors for the view of the plane using optional theme."""
if not "background" in theme: theme["background"] = (0,0,0)
if not "grid color" in theme: theme["grid color"] = (20,20,20)
if not "grid nscale" in theme: theme["grid nscale"] = 3
if not "show scale" in theme: theme["show scale"] = False
if not "show origin" in theme: theme["show origin"] = False
if not "show units" in theme: theme["show units"] = False
self.theme=theme
def createView(self,view=None):
"""Initializes the position and the units for the view of the plane using optional view."""
"""View must be a list of length 2."""
if view:
self.position= view[0]
self.units= view[1]
self.default_position= view[0][:]
self.default_units= view[1][:]
else:
self.default_position= [ 0, 0] #position of the center of the view in the plane's coordonnates
self.default_units= [40,40] #units of the conversion from window/plane
self.position= [ 0, 0]
self.units= [40,40] #units can be handled [-10**-14,10**307] before bugging or crashing
def __call__(self,window): #The main loop must be redefined by the client and not the functions called within it.
"""Main loop of the plane."""
window.rename("Plane")
while window.open:
window.check()
self.control(window)
self.clear(window)
self.show(window)
self.flip(window)
def contains(self,position,window):
"""Determine if a position is in the visible screen."""
x,y=position
xmin,ymin,xmax,ymax=self.getCorners(window)
return (xmin<=x<=xmax) and (ymin<=y<=ymax)
def flip(self,window):
"""Flip the plane's window."""
window.flip()
def clear(self,window,color=None):
"""Clear the plane."""
if not color: color=self.theme["background"]
window.clear(color)
def control(self,window):
"""Control the view of the plane."""
self.controlZoom(window)
self.controlPosition(window)
def controlZoom(self,window):
"""Control the zoom of the plane."""
keys=window.press()
ux,uy=self.units
k = self.zoom_sensibility
if keys[K_RSHIFT]:
self.zoom([1+k,1+k])
if keys[K_LSHIFT]:# and (ux>2 and uy>2): #The second condition is useless but prevent the user from watching errors due to too far zooming out.
self.zoom([1-k,1-k])
def controlPosition(self,window):
"""Control the position of the plane."""
keys=window.press()
if keys[K_RETURN]:
self.units= self.default_units[:]
self.position= self.default_position[:]
ux,uy=self.units
wsx,wsy=window.size
x,y=self.position
k=self.move_sensibility #Allow the user to move in the grid a reasonable velocity.
if keys[K_LEFT]:
x-=wsx/ux*k
if keys[K_RIGHT]:
x+=wsx/ux*k
if keys[K_DOWN]:
y-=wsy/uy*k
if keys[K_UP]:
y+=wsy/uy*k
self.position=[x,y]
def getUnitsColor(self,scale,ascale,color=None):
"""Get the color given a x position."""
if not color: color=self.theme["grid color"]
dscale=scale-ascale
return [c*(2**dscale) for c in color]
def show(self,window):
"""Show the elements on screen using the window."""
self.showGrids(window)
def showAll(self):
self.showGrids(window)
if self.theme["show scale"]:
window.print("scale:"+str(self.getScale(window)),(10,10),size=20)
if self.theme["show origin"] and self.contains((0,0),window):
self.showOrigin(window)
if self.theme["show units"]:
window.print("units:"+str(self.units),(10,10),size=20)
#self.showUnits(window) #Does not work for now
def showOrigin(self,window,color=colors.WHITE,radius=3):
"""Show the origin of the plane."""
position=self.getToScreen([0,0],window)
window.draw.circle(window.screen,color,position,radius,0)
def showUnits(self,window): #Not working because of error of synchronisation between text base system and normal window base.
"""Show the unit of the grid using the window."""
px,py=self.position
wsx,wsy=window.size
ux,uy=self.units
nx=int(wsx/ux)
ny=int(wsy/uy)
for y in range(int(-ny+py),int(ny+py)+1,10):
for x in range(int(-nx+px),int(nx+px)+1,10):
X,Y=self.getToScreen([x,y],window)
window.print(str([x,y]),[X,Y],size=20)
def showGrids(self,window,nscale=None):
"""Show the grid using the window and optional scales."""
ascale=self.getScale(window) #ascale like actual_scale
for scale in self.getScales(window,nscale,ascale):
self.showGrid(window,scale)
def getScales(self,window,nscale=None,scale=None):
"""Return the scales ."""
if not nscale: nscale=self.theme["grid nscale"]
if not scale: scale=self.getScale(window)
l=[scale-i for i in range(nscale)]
l.reverse()
return l
def getScale(self,window):
"""Return the actual scale of the displayed screen."""
ux,uy=self.units
wsx,wsy=window.size
uwx,uwy=wsx/ux,wsy/uy
uw=min(uwx,uwy)
return int(math.log(uw)/math.log(10))
def showGrid(self,window,scale=0,ascale=None):
"""Show the grid using the window."""
if not ascale: ascale=self.getScale(window)
xmin,ymin,xmax,ymax=self.getCorners(window) #Get the corners of the plane
#For each line find the begining and the end in the plane's coordonnates then convert it into screen's coordonnates.
for x in self.arange(xmin,xmax,scale):
color=self.getUnitsColor(scale,ascale)
start=self.getToScreen([x,ymin],window)
end= self.getToScreen([x,ymax],window)
window.draw.line(window.screen,color,start,end,1)
#Repeat the process for the y component
for y in self.arange(ymin,ymax,scale):
color=self.getUnitsColor(scale,ascale)
start=self.getToScreen([xmin,y],window)
end= self.getToScreen([xmax,y],window)
window.draw.line(window.screen,color,start,end,1)
def arange(self,min,max,scale):
"""Return the list of values that are between min and max, separated by unit."""
min=np.around(min,-scale)
max=np.around(max,-scale)
return np.arange(min,max,10**scale)
def zoom(self,zoom):
"""Allow the user to zoom into the plane."""
for i in range(2):
self.units[i]*=zoom[i]
def xZoom(self,zoom):
"""Allow the user to zoom into the plane according to the x component."""
self.units[0]*=zoom
def yZoom(self,zoom):
"""Allow the user to zoom into the plane according to the y component."""
self.units[1]*=zoom
def getToScreen(self,position,window):
"""Return a screen position using a position in the plane."""
x,y=position
px,py=self.position
ux,uy=self.units
wsx,wsy=window.size
x=int((x-px)*ux+wsx/2)
y=int(wsy/2-(y-py)*uy)
return [x,y]
def getAllToScreen(self,positions,window):
"""Return the list of screen positions using the list of plane positions."""
screen_positions=[]
for position in positions:
screen_positions.append(self.getToScreen(position,window))
return screen_positions
def getFromScreen(self,position,window):
"""Return a plane position using a position in the screen."""
x,y=position
px,py=self.position
ux,uy=self.units
wsx,wsy=window.size
x=(x-wsx/2)/ux+px
y=(wsy/2-y)/uy+py
return [x,y]
def getAllFromScreen(self,position,window):
"""Return the list of plane positions using the list of screen positions."""
plane_positions=[]
for position in positions:
planes_positions.append(self.getFromScreen(position,window))
return plane_positions
def getCorners(self,window):
"""Return the corners of the present view."""
wsx,wsy=window.size
ux,uy=self.units
x,y=self.position
lx,ly=wsx/ux,wsy/uy
return [x-lx/2,y-ly/2,x+lx/2,y+ly/2]
mx,my=self.getFromScreen([0,wsy],window)
Mx,My=self.getFromScreen([wsx,0],window)
return (mx,my,Mx,My)
def setCorners(self,corners,window):
"""Change the actual corners of the plane by changing its position and units."""
xmin,ymin,xmax,ymax=corners
wsx,wsy=window.size
self.position=[xmin+(xmax-xmin)/2,ymin+(ymax-ymin)/2]
self.units=[wsx/(xmax-xmin),wsy/(ymax-ymin)]
#The following functions are static methods
def getCornersFromCoordonnates(coordonnates):
"""Return the corners (top_left_corner,bottom_right_corner) using the coordonnates (position+size)."""
"""[x,y,sx,sy] -> [mx,my,Mx,My]"""
x,y,sx,sy=coordonnates
mx,my=x-sx/2,y-sy/2
Mx,My=x+sx/2,y+sy/2
corners=(mx,my,Mx,My)
return corners
def getCoordonnatesFromCorners(corners):
"""Return the coordonnates (position+size) using the corners (top_left_corner,bottom_right_corner)."""
"""[mx,my,Mx,My] -> [x,y,sx,sy]"""
mx,my,Mx,My=corners
sx,sy=Mx-mx,My-my
x,y=mx+sx/2,my+sy/2
coordonnates=(x,y,sx,sy)
return coordonnates
def getCoordonnatesFromRect(rect):
"""Return the coordonnates (position,size) using the rect (top_left_corner,size)."""
"""[x,y,sx,sy] -> [mx,my,sx,sy]"""
mx,my,sx,sy=rect
x,y=mx+sx/2,my+sy/2
coordonnates=[x,y,sx,sy]
return coordonnates
def getRectFromCoordonnates(coordonnates):
"""Return the rect (top_left_corner,size) using the coordonnates (position,size)."""
"""[mx,my,sx,sy] -> [x,y,sx,sy]"""
x,y,sx,sy=coordonnates
mx,my=x-sx/2,y-sy/2
rect=[mx,my,sx,sy]
return rect
def getRectFromCorners(corners):
"""Return the rect (top_left_corner,size) using the corners (top_left_corner,bottom_right_corner)."""
"""[mx,my,Mx,My] -> [mx,my,sx,sy]"""
mx,my,Mx,My=corners
sx,sy=Mx-mx,My-my
rect=[mx,my,sx,sy]
return rect
def getCornersFromRect(rect):
"""Return the (top_left_corner,bottom_right_corner) using the corners rect (top_left_corner,size)."""
"""[mx,my,Mx,My] -> [mx,my,sx,sy]"""
mx,my,sx,sy=rect
Mx,My=mx+sx,my+sy
corners=[mx,my,Mx,My]
return corners
if __name__=="__main__":
window=Window(fullscreen=False)
theme={"background":(0,0,0),"show scale":True,"show origin":True,"grid color":(200,200,200)}
plane=Plane(theme)
#plane.units=[1/10**300,1/10**300]
plane(window)