Why is my ball vector retaining its components after reinit? - vector

I'm writing a Pong game with Pygame and this is the code for my Ball class so far:
class Ball(pygame.sprite.Sprite):
def __init__(self, game, vector=Vec2D.Vec2D()):
super(Ball, self).__init__()
self.image = pygame.Surface((BALL_RADIUS*2, BALL_RADIUS*2))
self.rect = self.image.get_rect()
self.__draw_ball()
screen = pygame.display.get_surface()
self.area = screen.get_rect().inflate(-GAP*2, 0)
self.vector = vector
self.game = game
self.reinit()
def __draw_ball(self):
self.image.fill(BLACK)
self.image.set_colorkey(BLACK, RLEACCEL)
pygame.draw.circle(self.image, WHITE, (self.rect.centerx, self.rect.centery), BALL_RADIUS)
def reinit(self):
self.rect.centerx = self.area.centerx
self.rect.centery = self.area.centery
self.vector = Vec2D.Vec2D.from_magn_and_angle(BALL_SPEED, 0)
def update(self):
self.rect = self.calcnewpos()
self.handle_collision()
def calcnewpos(self):
(dx, dy) = self.vector.get_xy()
return self.rect.move(dx, dy)
def handle_collision(self):
(dx, dy) = self.vector.get_xy()
if not self.area.contains(self.rect):
if self.__hit_topbottom():
dy = -dy
elif self.__hit_leftright():
self.game.increase_score()
self.reinit()
else:
for paddle in self.hit_paddle(dx):
if dx < 0: self.rect.left = GAP + PADDLE_WIDTH
elif dx > 0: self.rect.right = SCREEN_WIDTH - (GAP + PADDLE_WIDTH)
dx = -dx
dy = paddle.hitpos / 4
paddle.collided = True
self.vector = Vec2D.Vec2D(dx, dy)
def _hit_topbottom(self):
return self.rect.top < 0 or self.rect.bottom > SCREEN_HEIGHT
def _hit_leftright(self):
return self.rect.left < self.area.left or self.rect.right > self.area.right
def hit_paddle(self, dx):
if dx < 0: paddle = self.game.paddles['left']
elif dx > 0: paddle = self.game.paddles['right']
if self.rect.colliderect(paddle.rect): return [paddle]
else: return []
Well, after the player (or the AI) scores, the ball calls its reinit method that places the ball in the middle of the screen and resets the vector:
def reinit(self):
self.rect.centerx = self.area.centerx
self.rect.centery = self.area.centery
self.vector = Vec2D.Vec2D.from_magn_and_angle(BALL_SPEED, 0)
But, somehow, the ball still maintains the vector it had before reinit was called. So when the ball gets through the left side with a vector like (-5.0 -2.0), it changes quickly in the reinit call and then change back to (-5.0, -2.0). Can someone please tell me why this is happening?

The problem, is in your handle_collision method.
The short story:
The first thing the function does, is set dx and dy to the current vector. Then it calls reinit(). Then, it sets them back to what the first were at the end of the function.
To fix:
Change
def handle_collision(self):
(dx, dy) = self.vector.get_xy() # <-- It first sets (dx, dy) to the old vector
if not self.area.contains(self.rect):
if self.__hit_topbottom():
...
elif self.__hit_leftright():
... # <-- here is where the reinit gets called, which changes the vector to new values.
else:
for paddle in self.hit_paddle(dx):
...
self.vector = Vec2D.Vec2D(dx, dy) # <-- Then the vector gets changed again, to the OLD vector saved above in (dx, dy)
To
def handle_collision(self):
(dx, dy) = self.vector.get_xy()
if not self.area.contains(self.rect):
if self.__hit_topbottom():
...
elif self.__hit_leftright():
...
return # <-- With the return here, the parsing never gets to change it back
else:
for paddle in self.hit_paddle(dx):
...
self.vector = Vec2D.Vec2D(dx, dy)
This will prevent the vector getting set back to what it was, while still setting it when it needs to be.

Related

Checking correctness of Bellman Ford Implementation in Python3

Below is my code in Python. However, how can I assure its correctness (not the efficiency) other than providing some test cases? Could anyone tell me it's wrong or correct?
def bellman_ford(n_vertice: int, E: list[tuple], source=1) -> bool:
''' returns True (no negative cycle) / False (cycle) and distance array '''
# data import
edges = {}
for weighted_edge in E:
u, v, w = weighted_edge
edges[(u, v)] = w # u --w--> v
# set initial distance
distance = [None] + [0] + [float('inf') for _ in range(2, n_vertice + 1)]
# relax
for round in range(1, n_vertice + 1): # for n rounds ...
for edge, w in edges.items(): # relax all edges
u, v = edge
if distance[v] > distance[u] + w:
if round != n_vertice:
distance[v] = distance[u] + w
else: # n-th round for cycle detection
return False, distance
return True, distance

Quick Select in O(1) using tail recursion

I was doing a ZTM course there he said that Quick Select algo at worst case can be O(1) Space complexity using tail recursion as it only makes one recursive calls.
His Code was
const quickSelect = function (nums, left, right, indexToFind) {
const partitionIndex = getPartition(nums, left, right);
if (partitionIndex === indexToFind) {
return nums[partitionIndex];
} else if (indexToFind < partitionIndex) {
return quickSelect(nums, left, partitionIndex - 1, indexToFind);
} else {
return quickSelect(nums, partitionIndex + 1, right, indexToFind);
}
};
And here is the optimized version of Quick Sort from GFG article it looks like quickSelect and one recursive call each time.
Code:
def quickSort(arr, low, high)
{
while (low < high):
''' pi is partitioning index, arr[p] is now
at right place '''
pi = partition(arr, low, high);
# If left part is smaller, then recur for left
# part and handle right part iteratively
if (pi - low < high - pi):
quickSort(arr, low, pi - 1);
low = pi + 1;
# Else recur for right part
else:
quickSort(arr, pi + 1, high);
high = pi - 1;
}
Why can't this be O(1) Space complexity then using Tail rec?
Iterative version of quick select using Lomuto partition scheme. I don't know if python optimization handles tail recursion.
def quickselect(a, k):
lo = 0
hi = len(a)-1
while (lo < hi):
p = a[hi]; # Lomuto partition
i = lo;
for j in range(lo, hi):
if (a[j] < p):
a[i],a[j] = a[j],a[i]
i += 1
a[i],a[hi] = a[hi],a[i]
if (k == i): # if pivot == kth element, return it
return a[k]
if (k < i): # loop on partition with kth element
hi = i - 1
else:
lo = i + 1
return a[k] # sorted to kth elemement, return it
Iterative version of quick select using Hoare partition scheme, it has to loop until lo == hi, since the pivot and values equal to pivot can end up anywhere after a partition step. However, it is faster than Lomuto partition scheme (at least with Python).
def quickselect(a, k):
lo = 0
hi = len(a)-1
while (lo < hi):
p = a[(lo + hi) // 2] # Hoare partition
i = lo - 1
j = hi + 1
while(1):
while(1):
i += 1
if(a[i] >= p):
break
while(1):
j -= 1
if(a[j] <= p):
break
if(i >= j):
break
a[i],a[j] = a[j],a[i]
if (k <= j): # loop on partition with kth element
hi = j
else:
lo = j + 1
return a[k] # sorted to kth elemement, return it

Finding if a circle is fully contained within multiple triangles?

In a game, an area is defined by triangles that never overlap, and characters are defined by circles.
How can I know whether the full character's collision circle is contained within these triangles?
Example image:
Here, the red parts are outside triangles, so the circle isn't contained within them. Is there an algorithm that can detect this?
I've only came up with "non-perfect" solutions, like sampling points at the border of the circle, then testing if each is inside a triangle.
So basically, the triangles form a domain with polygonal boundary and you want to check if a disk, defined by a center point and a radius is contained inside the domain. So if you start with the triangles, you have to find a way to extract the polygonal boundary of your domain and represent it as a 2D array (matrix) of shape n rows and two columns so that every row is the two coordinates of a vertex point of the polygonal boundary line and the points are ordered so that they are consecutive order along the boundary in a counterclockwise position, i.e. when you walk in a direction from point of index i to the next point i+1 the domain stays on your left. For example, here is the representation of a polygonal boundary of a domain like yours:
a = 4/math.sqrt(3)
Pgon = np.array([[0,0],
[a,0],
[2*a,-1],
[2*a+4,0],
[2*a+4,4],
[2*a,4],
[2*a,2],
[a,1],
[a,4],
[0,0]])
Observe that the first and the last points are the same.
In such a scenario, maybe you can try the following algorithm:
import numpy as np
import math
def angle_and_dist(p1, p2, o):
p12 = p2 - p1
op1 = p1 - o
op2 = p2 - o
norm_p12 = math.sqrt(p12[0]**2 + p12[1]**2)
norm_op1 = math.sqrt(op1[0]**2 + op1[1]**2)
norm_op2 = math.sqrt(op2[0]**2 + op2[1]**2)
p12_perp = np.array([ - p12[1], p12[0] ])
h = - op1.dot(p12_perp)
theta12 = op1.dot(op2) / (norm_op1*norm_op2)
theta12 = math.acos( theta12 )
if h < 0:
theta12 = - theta12
if op1.dot(p12) > 0:
return theta12, norm_op1
elif op2.dot(p12) < 0:
return theta12, norm_op2
else:
return theta12, h/norm_p12
def is_in_polygon(p, disk):
o, r = disk
n_p = len(p)-1
index_o = 0
h_min = 400
for i in range(n_p):
theta, h = angle_and_dist(p[i,:], p[i+1,:], o)
index_o = index_o + theta
if 0 <= h and h < h_min:
h_min = h
if theta <= math.pi/100:
return 'center of disc is not inside polygon'
elif theta > math.pi/100:
if h_min > r:
return 'disc is inside polygon'
else:
return 'center of disc is inside polygon but disc is not'
a = 4/math.sqrt(3)
Pgon = np.array([[0,0],
[a,0],
[2*a,-1],
[2*a+4,0],
[2*a+4,4],
[2*a,4],
[2*a,2],
[a,1],
[a,4],
[0,0]])
# A test example:
#disc = (np.array([3*a/4, 2]), a/4-0.001)
disc = (np.array([3*a/4, 2]), math.sqrt(3)*a/8 - 0.0001)
print(is_in_polygon(Pgon, disc))

Pygame accurate shooting in game

I have simplified my code to the bare necessities to make it easier to understand. All that happens is when a certain area is clicked a bullet fires to around that area originating from a white square. The problem is that I don't want the bullet to hit around the area clicked, but exactly where it was clicked.
Here is my code:
import pygame, sys, time, random, math
from pygame.locals import *
background = (0, 0, 0)
entity_color = (255, 255, 255,255)
listLaser = pygame.sprite.Group()
player_x, player_y = 0, 0
move_player_x, move_player_y = 0, 0
all_sprites_list = pygame.sprite.Group()
class Entity(pygame.sprite.Sprite):
"""Inherited by any object in the game."""
def __init__(self, x, y, width, height):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.width = width
self.height = height
# This makes a rectangle around the entity, used for anything
# from collision to moving around.
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
class User(Entity):
"""
Player controlled or AI controlled, main interaction with
the game
"""
def __init__(self, x, y, width, height):
super(User, self).__init__(x, y, width, height)
self.image = pygame.Surface([20, 37])
self.image.fill(entity_color)
self.image.blit(self.image, (0, 0))
class Player(User):
"""The player controlled Character"""
def __init__(self, x, y, width, height):
super(Player, self).__init__(x, y, width, height)
pass
class Bullet(pygame.sprite.Sprite):
def __init__(self, mouse, player):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([4, 10])
self.image.fill(entity_color)
self.mouse_x, self.mouse_y = mouse[0], mouse[1]
self.player = player
self.rect = self.image.get_rect()
def update(self):
'''
Gets vector from the two points and gets a direction and sends the bullet that way using player and clicked points
'''
speed = -4.
range = 200
distance = [self.mouse_x - self.player[0], self.mouse_y - self.player[1]]
norm = math.sqrt(distance[0] ** 2 + distance[1] ** 2)
direction = [distance[0] / norm, distance[1 ] / norm]
bullet_vector = [direction[0] * speed, direction[1] * speed]
self.rect.x -= bullet_vector[0]
self.rect.y -= bullet_vector[1]
pygame.init()
pygame.display.set_caption('Race')
window_width = 800
window_height = 700
screen = pygame.display.set_mode((window_width, window_height))
player = Player(20, window_height / 2, 40, 37)
all_sprites_list.add(player)
while True: # the main game loop
for event in pygame.event.get():
if event.type == MOUSEBUTTONDOWN:
bullet = Bullet(pygame.mouse.get_pos(), [player.rect.x, player.rect.y])
bullet.rect.x = player.rect.x
bullet.rect.y = player.rect.y
all_sprites_list.add(bullet)
listLaser.add(bullet)
for ent in all_sprites_list:
ent.update()
screen.fill(background)
all_sprites_list.draw(screen)
pygame.display.flip()
pygame.display.update()
I really need help modifying my code to get the bullet to hit exactly at the clicked area
pygame.Rects can't have floating point numbers as their x, y attributes/coordinates and pygame just truncates the floats that you assign to the rect, so the direction becomes inaccurate.
You need to store the actual position in separate attributes (for example self.posx) and then update these attributes first and afterwards the self.rect.
class Bullet(pygame.sprite.Sprite):
def __init__(self, mouse, player):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([4, 10])
self.image.fill(entity_color)
self.mouse_x, self.mouse_y = mouse[0], mouse[1]
self.player = player
self.posx = self.player[0]
self.posy = self.player[1]
self.rect = self.image.get_rect()
speed = -4.
distance = [self.mouse_x - self.player[0], self.mouse_y - self.player[1]]
norm = math.sqrt(distance[0] ** 2 + distance[1] ** 2)
direction = [distance[0] / norm, distance[1 ] / norm]
self.bullet_vector = [direction[0] * speed, direction[1] * speed]
def update(self):
self.posx -= self.bullet_vector[0]
self.posy -= self.bullet_vector[1]
self.rect.topleft = self.posx, self.posy
You also don't need to calculate the bullet_vector every frame unless the bullets should be able to change their direction (like a homing missile).

Using atan2 to find angle between two vectors

I understand that:
atan2(vector.y, vector.x) = the angle between the vector and the X axis.
But I wanted to know how to get the angle between two vectors using atan2. So I came across this solution:
atan2(vector1.y - vector2.y, vector1.x - vector2.x)
My question is very simple:
Will the two following formulas produce the same number?
atan2(vector1.y - vector2.y, vector1.x - vector2.x)
atan2(vector2.y - vector1.y, vector2.x - vector1.x)
If not: How do I know what vector comes first in the subtractions?
atan2(vector1.y - vector2.y, vector1.x - vector2.x)
is the angle between the difference vector (connecting vector2 and vector1) and the x-axis,
which is problably not what you meant.
The (directed) angle from vector1 to vector2 can be computed as
angle = atan2(vector2.y, vector2.x) - atan2(vector1.y, vector1.x);
and you may want to normalize it to the range [0, 2 π):
if (angle < 0) { angle += 2 * M_PI; }
or to the range (-π, π]:
if (angle > M_PI) { angle -= 2 * M_PI; }
else if (angle <= -M_PI) { angle += 2 * M_PI; }
A robust way to do it is by finding the sine of the angle using the cross product, and the cosine of the angle using the dot product and combining the two with the Atan2() function.
In C# this is:
public struct Vector2
{
public double X, Y;
/// <summary>
/// Returns the angle between two vectos
/// </summary>
public static double GetAngle(Vector2 A, Vector2 B)
{
// |A·B| = |A| |B| COS(θ)
// |A×B| = |A| |B| SIN(θ)
return Math.Atan2(Cross(A,B), Dot(A,B));
}
public double Magnitude { get { return Math.Sqrt(Dot(this,this)); } }
public static double Dot(Vector2 A, Vector2 B)
{
return A.X*B.X+A.Y*B.Y;
}
public static double Cross(Vector2 A, Vector2 B)
{
return A.X*B.Y-A.Y*B.X;
}
}
class Program
{
static void Main(string[] args)
{
Vector2 A=new Vector2() { X=5.45, Y=1.12};
Vector2 B=new Vector2() { X=-3.86, Y=4.32 };
double angle=Vector2.GetAngle(A, B) * 180/Math.PI;
// angle = 120.16850967865749
}
}
See the test case above in GeoGebra.
I think a better formula was posted here:
http://www.mathworks.com/matlabcentral/answers/16243-angle-between-two-vectors-in-3d
angle = atan2(norm(cross(a,b)), dot(a,b))
So this formula works in 2 or 3 dimensions.
For 2 dimensions this formula simplifies to the one stated above.
Nobody pointed out that if you have a single vector, and want to find the angle of the vector from the X axis, you can take advantage of the fact that the argument to atan2() is actually the slope of the line, or (delta Y / delta X). So if you know the slope, you can do the following:
given:
A = angle of the vector/line you wish to determine (from the X axis).
m = signed slope of the vector/line.
then:
A = atan2(m, 1)
Very useful!
If you care about accuracy for small angles, you want to use this:
angle = 2*atan2(|| ||b||a - ||a||b ||, || ||b||a + ||a||b ||)
Where "||" means absolute value, AKA "length of the vector". See https://math.stackexchange.com/questions/1143354/numerically-stable-method-for-angle-between-3d-vectors/1782769
However, that has the downside that in two dimensions, it loses the sign of the angle.
As a complement to the answer of #martin-r one should note that it is possible to use the sum/difference formula for arcus tangens.
angle = atan2(vec2.y, vec2.x) - atan2(vec1.y, vec1.x);
angle = -atan2(vec1.x * vec2.y - vec1.y * vec2.x, dot(vec1, vec2))
where dot = vec1.x * vec2.x + vec1.y * vec2.y
Caveat 1: make sure the angle remains within -pi ... +pi
Caveat 2: beware when the vectors are getting very similar, you might get extinction in the first argument, leading to numerical inaccuracies
You don't have to use atan2 to calculate the angle between two vectors. If you just want the quickest way, you can use dot(v1, v2)=|v1|*|v2|*cos A
to get
A = Math.acos( dot(v1, v2)/(v1.length()*v2.length()) );
angle(vector.b,vector.a)=pi/2*((1+sgn(xa))*(1-sgn(ya^2))-(1+sgn(xb))*(1-sgn(yb^2)))
+pi/4*((2+sgn(xa))*sgn(ya)-(2+sgn(xb))*sgn(yb))
+sgn(xa*ya)*atan((abs(xa)-abs(ya))/(abs(xa)+abs(ya)))
-sgn(xb*yb)*atan((abs(xb)-abs(yb))/(abs(xb)+abs(yb)))
xb,yb and xa,ya are the coordinates of the two vectors
The formula, angle(vector.b,vector.a), that I sent, give results
in the four quadrants and for any coordinates xa,ya and xb,yb.
For coordinates xa=ya=0 and or xb=yb=0 is undefined.
The angle can be bigger or smaller than pi, and can be positive
or negative.
Here a little program in Python that uses the angle between vectors to determine if a point is inside or outside a certain polygon
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from shapely.geometry import Point, Polygon
from pprint import pprint
# Plot variables
x_min, x_max = -6, 12
y_min, y_max = -3, 8
tick_interval = 1
FIG_SIZE = (10, 10)
DELTA_ERROR = 0.00001
IN_BOX_COLOR = 'yellow'
OUT_BOX_COLOR = 'black'
def angle_between(v1, v2):
""" Returns the angle in radians between vectors 'v1' and 'v2'
The sign of the angle is dependent on the order of v1 and v2
so acos(norm(dot(v1, v2))) does not work and atan2 has to be used, see:
https://stackoverflow.com/questions/21483999/using-atan2-to-find-angle-between-two-vectors
"""
arg1 = np.cross(v1, v2)
arg2 = np.dot(v1, v2)
angle = np.arctan2(arg1, arg2)
return angle
def point_inside(point, border):
""" Returns True if point is inside border polygon and False if not
Arguments:
:point: x, y in shapely.geometry.Point type
:border: [x1 y1, x2 y2, ... , xn yn] in shapely.geomettry.Polygon type
"""
assert len(border.exterior.coords) > 2,\
'number of points in the polygon must be > 2'
point = np.array(point)
side1 = np.array(border.exterior.coords[0]) - point
sum_angles = 0
for border_point in border.exterior.coords[1:]:
side2 = np.array(border_point) - point
angle = angle_between(side1, side2)
sum_angles += angle
side1 = side2
# if wn is 1 then the point is inside
wn = sum_angles / 2 / np.pi
if abs(wn - 1) < DELTA_ERROR:
return True
else:
return False
class MainMap():
#classmethod
def settings(cls, fig_size):
# set the plot outline, including axes going through the origin
cls.fig, cls.ax = plt.subplots(figsize=fig_size)
cls.ax.set_xlim(-x_min, x_max)
cls.ax.set_ylim(-y_min, y_max)
cls.ax.set_aspect(1)
tick_range_x = np.arange(round(x_min + (10*(x_max - x_min) % tick_interval)/10, 1),
x_max + 0.1, step=tick_interval)
tick_range_y = np.arange(round(y_min + (10*(y_max - y_min) % tick_interval)/10, 1),
y_max + 0.1, step=tick_interval)
cls.ax.set_xticks(tick_range_x)
cls.ax.set_yticks(tick_range_y)
cls.ax.tick_params(axis='both', which='major', labelsize=6)
cls.ax.spines['left'].set_position('zero')
cls.ax.spines['right'].set_color('none')
cls.ax.spines['bottom'].set_position('zero')
cls.ax.spines['top'].set_color('none')
#classmethod
def get_ax(cls):
return cls.ax
#staticmethod
def plot():
plt.tight_layout()
plt.show()
class PlotPointandRectangle(MainMap):
def __init__(self, start_point, rectangle_polygon, tolerance=0):
self.current_object = None
self.currently_dragging = False
self.fig.canvas.mpl_connect('key_press_event', self.on_key)
self.plot_types = ['o', 'o-']
self.plot_type = 1
self.rectangle = rectangle_polygon
# define a point that can be moved around
self.point = patches.Circle((start_point.x, start_point.y), 0.10,
alpha=1)
if point_inside(start_point, self.rectangle):
_color = IN_BOX_COLOR
else:
_color = OUT_BOX_COLOR
self.point.set_color(_color)
self.ax.add_patch(self.point)
self.point.set_picker(tolerance)
cv_point = self.point.figure.canvas
cv_point.mpl_connect('button_release_event', self.on_release)
cv_point.mpl_connect('pick_event', self.on_pick)
cv_point.mpl_connect('motion_notify_event', self.on_motion)
self.plot_rectangle()
def plot_rectangle(self):
x = [point[0] for point in self.rectangle.exterior.coords]
y = [point[1] for point in self.rectangle.exterior.coords]
# y = self.rectangle.y
self.rectangle_plot, = self.ax.plot(x, y,
self.plot_types[self.plot_type], color='r', lw=0.4, markersize=2)
def on_release(self, event):
self.current_object = None
self.currently_dragging = False
def on_pick(self, event):
self.currently_dragging = True
self.current_object = event.artist
def on_motion(self, event):
if not self.currently_dragging:
return
if self.current_object == None:
return
point = Point(event.xdata, event.ydata)
self.current_object.center = point.x, point.y
if point_inside(point, self.rectangle):
_color = IN_BOX_COLOR
else:
_color = OUT_BOX_COLOR
self.current_object.set_color(_color)
self.point.figure.canvas.draw()
def remove_rectangle_from_plot(self):
try:
self.rectangle_plot.remove()
except ValueError:
pass
def on_key(self, event):
# with 'space' toggle between just points or points connected with
# lines
if event.key == ' ':
self.plot_type = (self.plot_type + 1) % 2
self.remove_rectangle_from_plot()
self.plot_rectangle()
self.point.figure.canvas.draw()
def main(start_point, rectangle):
MainMap.settings(FIG_SIZE)
plt_me = PlotPointandRectangle(start_point, rectangle) #pylint: disable=unused-variable
MainMap.plot()
if __name__ == "__main__":
try:
start_point = Point([float(val) for val in sys.argv[1].split()])
except IndexError:
start_point= Point(0, 0)
border_points = [(-2, -2),
(1, 1),
(3, -1),
(3, 3.5),
(4, 1),
(5, 1),
(4, 3.5),
(5, 6),
(3, 4),
(3, 5),
(-0.5, 1),
(-3, 1),
(-1, -0.5),
]
border_points_polygon = Polygon(border_points)
main(start_point, border_points_polygon)

Resources