First of all, I'm fairly sure snapping to grid is fairly easy, however I've run into some odd trouble in this situation and my maths are too weak to work out specifically what is wrong.
Here's the situation
I have an abstract concept of a grid, with Y steps exactly Y_STEP apart (the x steps are working fine so ignore them for now)
The grid is in an abstract coordinate space, and to get things to line up I've got a magic offset in there, let's call it Y_OFFSET
to snap to the grid I've got the following code (python)
def snapToGrid(originalPos, offset, step):
index = int((originalPos - offset) / step) #truncates the remainder away
return index * gap + offset
so I pass the cursor position, Y_OFFSET and Y_STEP into that function and it returns me the nearest floored y position on the grid
That appears to work fine in the original scenario, however when I take into account the fact that the view is scrollable things get a little weird.
Scrolling is made as basic as I can get it, I've got a viewPort that keeps count of the distance scrolled along the Y Axis and just offsets everything that goes through it.
Here's a snippet of the cursor's mouseMotion code:
def mouseMotion(self, event):
pixelPos = event.pos[Y]
odePos = Scroll.pixelPosToOdePos(pixelPos)
self.tool.positionChanged(odePos)
So there's two things to look at there, first the Scroll module's translation from pixel position to the abstract coordinate space, then the tool's positionChanged function which takes the abstract coordinate space value and snaps to the nearest Y step.
Here's the relevant Scroll code
def pixelPosToOdePos(pixelPos):
offsetPixelPos = pixelPos - self.viewPortOffset
return pixelsToOde(offsetPixelPos)
def pixelsToOde(pixels):
return float(pixels) / float(pixels_in_an_ode_unit)
And the tools update code
def positionChanged(self, newPos):
self.snappedPos = snapToGrid(originalPos, Y_OFFSET, Y_STEP)
The last relevant chunk is when the tool goes to render itself. It goes through the Scroll object, which transforms the tool's snapped coordinate space position into an onscreen pixel position, here's the code:
#in Tool
def render(self, screen):
Scroll.render(screen, self.image, self.snappedPos)
#in Scroll
def render(self, screen, image, odePos):
pixelPos = self.odePosToPixelPos(odePos)
screen.blit(image, pixelPos) # screen is a surface from pygame for the curious
def odePosToPixelPos(self.odePos):
offsetPos = odePos + self.viewPortOffset
return odeToPixels(offsetPos)
def odeToPixels(odeUnits):
return int(odeUnits * pixels_in_an_ode_unit)
Whew, that was a long explanation. Hope you're still with me...
The problem I'm now getting is that when I scroll up the drawn image loses alignment with the cursor.
It starts snapping to the Y step exactly 1 step below the cursor.
Additionally it appears to phase in and out of allignment.
At some scrolls it is out by 1 and other scrolls it is spot on.
It's never out by more than 1 and it's always snapping to a valid grid location.
Best guess I can come up with is that somewhere I'm truncating some data in the wrong spot, but no idea where or how it ends up with this behavior.
Anyone familiar with coordinate spaces, scrolling and snapping?
Ok, I'm answering my own question here, as alexk mentioned, using int to truncate was my mistake.
The behaviour I'm after is best modeled by math.floor().
Apologies, the original question does not contain enough information to really work out what the problem is. I didn't have the extra bit of information at that point.
With regards to the typo note, I think I may be using the context in a confusing manner... From the perspective of the positionChanged() function, the parameter is a new position coming in.
From the perspective of the snapToGrid() function the parameter is an original position which is being changed to a snapped position.
The language is like that because part of it is in my event handling code and the other part is in my general services code. I should have changed it for the example
Do you have a typo in positionChanged() ?
def positionChanged(self, newPos):
self.snappedPos = snapToGrid(newPos, Y_OFFSET, Y_STEP)
I guess you are off by one pixel because of the accuracy problems during float division. Try changing your snapToGrid() to this:
def snapToGrid(originalPos, offset, step):
EPS = 1e-6
index = int((originalPos - offset) / step + EPS) #truncates the remainder away
return index * gap + offset
Thanks for the answer, there may be a typo, but I can't see it...
Unfortunately the change to snapToGrid didn't make a difference, so I don't think that's the issue.
It's not off by one pixel, but rather it's off by Y_STEP. Playing around with it some more I've found that I can't get it to be exact at any point that the screen is scrolled up and also that it happens towards the top of the screen, which I suspect is ODE position zero, so I'm guessing my problem is around small or negative values.
Related
This is my first question ever, and I am a complete and utter beginner, so please don't eat me :) What I am trying to to is to draw a fibonacci sequence using the Python turtle module. My code is as follows:
import turtle
zuf = turtle.Turtle()
while True:
zuf.forward(10)
zuf.left(3.1415)
This, however, drives around in circles only. I have tried to create a variable, say X, and assign a fibonacci rule to it xn = xn-1 + xn-2 then I'd put it in here zuf.forward(x) but it doesn't work. I tried multiple variations of that, but none seems to work. Please don't give a whole solution, only some hint, thanks a lot.
I think I can get you from where you are to where you want to be. First, your invocation of:
zuf.left(3.1415)
seems to indicate you're thinking in radians, which is fine. But you need to tell your turtle that:
zuf = turtle.Turtle()
zuf.radians()
this will still make your code go in circles, but very different circles. Next, we want to replace 10 with our fibonacci value. Before the while loop, initialize your fibonacci counters:
previous, current = 0, 1
as the last statement in the while loop, bump them up:
previous, current = current, current + previous
and in your forward() call, replace 10 with current. Next, we need to turn the line that it's drawing into a square. To do this, we need to do two things. First, loop the drawing code four times:
for i in range(4):
zuf.forward(current)
zuf.left(3.1415)
And second, replace your angle with pi/2 instead:
zuf.left(3.1415 / 2)
If you assemble this all correctly, you should end up with a figure like:
showing the increasing size of the fibonacci values. Not the greatest looking image, you'll still have to do some work on it to clean it up to look nice.
Finally, I was impressed with the fibonacci drawing code that #IvanS95 linked to in his comment, that I wrote a high speed version of it that uses stamping instead of drawing:
from turtle import Screen, Turtle
SCALE = 5
CURSOR_SIZE = 20
square = Turtle('square', visible=False)
square.fillcolor('white')
square.speed('fastest')
square.right(90)
square.penup()
previous_scaled, previous, current = 0, 0, 1
for _ in range(10):
current_scaled = current * SCALE
square.forward(current_scaled/2 + previous_scaled/2)
square.shapesize(current_scaled / CURSOR_SIZE)
square.left(90)
square.forward(current_scaled/2 - previous_scaled/2)
square.stamp()
previous_scaled, previous, current = current_scaled, current, current + previous
screen = Screen()
screen.exitonclick()
This is not a whole solution for you, only a hint of what can be done as you're drawing your squares and this is a stamp-based solution which plays by different rules.
I'm relatively new to Python, and I'm working with the tkinter canvas. I currently use
pos = canvas.coords(object)
speed = 5 #could be any number though
destpos = canvas.coords(destination)
xdist = destpos[2]-pos[2]
ydist = destpos[3]-pos[3]
#finds hypotenuse of an imaginary right triangle
fraction = speed/math.sqrt(xdist**2+ydist**2)
#puts the values into ratio so the object knows how far x and y they need to go
x = xdist * fraction
y = ydist * fraction
#canvas.move() function so the object moves
canvas.move(self.id, x, y)
However, I'd like a simple already added method of moving an object to another destination at a given speed.
I need to use this code dozens of times in an application I'm working on, and I'd prefer using a simpler method compared to a function.
In summary, what I want is a simple method of completing the same task that is a bit easier to understand. (The method is a bit buggy and I can't ever figure out why)
I am trying to understand and code a python script that create a random-dot stereogram (RDS) from a depthmap and a random-dot generated pattern. From what I've understood, to create the illusion of depth, pixels are shifted so when we make them merge by changing focus the difference of shifting creates the illusion.
I put this into practice with this depth map:
Here is the result:
But I don't understand why I can see on the result 2 objects, 1 star "close" to me and an other star "far" from me. And there is different possible results depending of how I focus my eyes.
I have read many things on the subject but I don't get it. Maybe the problem is my poor english or understanding of what I've read but I will appreciate some detailed explanations since there not that much technical explanations on the web about how to code this from scratch.
Note: I have tried with different size on shift and pattern and it doesn't seem to change anything
Code: (Tell me if you need other part of the code or some comment about how it work. I didn't clean it yet)
import os, sys
import pygame
def get_linked_point(depthmap, d_width, d_height, sep):
"""
In this function we link each pixel in white in the depth map with the
coordinate of the shifted pixel we will need to create the illusion
ex: [[x,y],[x_shifted,y]]
:param sep: is the shift value in pixels
"""
deptharray = pygame.PixelArray(depthmap)
list_linked_point = []
for x in range(d_width):
for y in range(d_height):
if deptharray[x][y] != 0x000000:
list_linked_point.append([[x, y], [x+sep, y]])
del deptharray
return list_linked_point
def display_stereogram(screen, s_width, pattern, p_width, linked_points):
"""
Here we fill the window with the pattern. Then for each linked couple of
point we make the shifted pixel [x_shifted,y] equal to the other one
[x,y]
"""
x = 0
while x < s_width:
screen.blit(pattern, [x, 0])
x += p_width
pixAr = pygame.PixelArray(screen)
for pair in linked_points:
pixAr[pair[0][0], pair[0][1]] = pixAr[pair[1][0], pair[1][1]]
del pixAr
The problem "I can see on the result 2 objects, 1 star "close" to me and an other star "far" from me" is due to the fact that I get the wrong approach when I try to generalize my understanding of stereograms made with 2 images to stereograms using repeated pattern.
To create 2 images stereograms you need to shift pixels of one image to make the depth illusion.
What was wrong in my approch is that I only shift pixels that should create the star. What I didn't get is that because RDS are made by repeated patterns, shifting these pixels also create an opposite shifting with next patterns creating an other star of the opposite depth.
To correct this I paired every point of the depth map (not only the white one) in order to come back to the base shifting amount after the end of the star.
Here is the result:
Code: (This code is the previous one quickly modified after the help of Neil Slater so it's not clean yet. I will try to improve this)
def get_linked_point(depthmap, d_width, d_height, p_width, sep):
"""
In this function we link each pixel in white in the depth map with the
coordinate of the shifted pixel we will need to create the illusion
ex: [[x,y],[x_shifted,y]]
:param sep: is the shift value in pixels
"""
deptharray = pygame.PixelArray(depthmap)
list_linked_point = []
for x in range(d_width):
for y in range(d_height):
if deptharray[x][y] == 0x000000:
list_linked_point.append([[x, y], [x+p_width, y]])
else:
list_linked_point.append([[x, y], [x-sep+p_width, y]])
del deptharray
return list_linked_point
def display_stereogram(screen, s_width, pattern, p_width, linked_points):
"""
Here we fill the window with the pattern. Then for each linked couple of
point we make the shifted pixel [x_shifted,y] equal to the other one
[x,y]
"""
x = 0
while x < s_width:
screen.blit(pattern, [x, 0])
x += p_width
pixAr = pygame.PixelArray(screen)
for pair in linked_points:
pixAr[pair[1][0], pair[1][1]] = pixAr[pair[0][0], pair[0][1]]
del pixAr
this may be just as much a maths problem than a code problem. I decided to learn how 3d engines work, and i'm following http://petercollingridge.appspot.com/3D-tutorial/rotating-objects this guide, but converting the code to python. in the function for rotating on the Z-axis, my code looks like this:
def rotate_z(theta):
theta=math.radians(theta)
for i in ords:
i[0]= i[0]*math.cos(theta) - i[1]* math.sin(theta)
i[1]= i[1]*math.cos(theta) + i[0]* math.sin(theta)
which rotates the node the appropriate amount, but over maybe 5 seconds, or 150 frames, the nodes start to slowly move together, until, about 20 seconds in, they coalesce. my initial thought was that it was a round down on the last two lines, but i am stuck. any ideas anyone?
It looks like the problem is that you're changing the value of i[0] when you need the old value to set i[1]:
i[0]= i[0]*math.cos(theta) - i[1]*math.sin(theta) <-- You change the value of i[0]
i[1]= i[1]*math.cos(theta) + i[0]*math.sin(theta) <-- You use the changed value of i[0], not the original
So the value of i[0] gets replaced, when you still want to keep it.
You can solve this by using separate variables (as Peter Collingridge does):
for i in ords:
x = i[0]
y = i[1]
i[0]= x*math.cos(theta) - y*math.sin(theta)
i[1]= y*math.cos(theta) + x*math.sin(theta)
This way, you should not get the "feedback loop" which results in the points gradually floating together.
stuck on a simple task any help would be much appreciated. It creates a graphic window, and depending on where the user clicks it will draw a different colour circle. Right hand = Yellow and the left hand will be red depending on where the user clicks. However i cant get my if statement to work, and all its returning is 10 yellow circles. Any help would be appreciated , thanks
def circles():
win = GraphWin ("circles", 400,100)
for i in range (10):
point = win.getMouse()
circleFill = Circle(point, 10)
circleFill.draw(win)
if str(point) >= str(200):
circleFill = Circle (point, 10)
circleFill.setFill("Yellow")
circleFill.draw(win)
else:
circleFill = Circle (point, 10)
circleFill.setFill("Red")
circleFill.draw(win)
You're trying to compare a point to a number. This doesn't make any sense. Is the upper-right corner of your screen more than 200? What about the lower-right? Or the upper-left?
Of course you can convert them both to strings, then compare those, because you can always compare strings—but then you're just asking whether something like 'Point(1600, 0)' would comes before or after '200' in the dictionary, which doesn't tell you anything useful.
Your next attempt, trying to compare a point to a point, still doesn't make any sense. Is (1600, 20) more or less than (100, 1280)? Of course there are various ways you could answer that (e.g., you could treat them as vectors rather than points and ask for their norms), but nothing that seems relevant to your question.
I think what you might want to do here is to compare the X coordinate of the point to a number:
if point.getX() >= 200:
That makes sense. That covers the whole right part of the screen, whether way up at the top or way down at the bottom, because whether you're at (1600, 0) or (1600, 1200), that 1600 part is bigger than 200.
That may not actually be what you want, but hopefully if it isn't, it gives you the idea to get unstuck.