BruceSherwood/vpython-wx

Settting frame.axis can flip display 180 degrees

Closed this issue · 10 comments

Reported by Geoff Tovey, bug in setting frame.axis (display flips in some orientations):

from visual import *

f = frame()
box() # marks frame origin (centre of rotation)

c = curve(frame=f, pos=[(0,0,0),(8,0,0),
(8,0,4),(8,2,4)], color=color.cyan)

while True:
for angle in range(0,360):
rate(100)
angleR = radians(angle)
f.axis = ( cos(angleR), sin(angleR), 0) # this fails
f.rotate(angle=radians(1), axis=(0,0,1)) # this works
if f.frame_to_world( (0,0,10)).z < 0:
c.color = color.red
else:
c.color = color.cyan

Later Tovey reported that setting frame.axis to (0,0,1) is a workaround for this specific case. But obviously the bug needs to be addressed.

Tovey provided the following simpler test routine in which a flip is seen when xx reachs -21.

letter = text( text="R", up=(0,1,0)) # upright
letter.axis = (0,100,0)

while True:
for xx in range(0, -25, -1): # x coord of letter.axis
letter.axis = (xx, 100, 0)
print xx, # flips at xx = -21
scene.waitfor('click')

Also, the following statements fail to affect letter.axis:

   letter.axis.x = xx # setting axis.x has no effect on axis
   print letter.axis

With compliments of the season - I'm curious if this got fixed. I'm getting similar behaviour trying to model a sculling boat. I create the oars within a frame, then manipulate the frame to create the movement of the oar through the stroke. Specifically, I model the feathering of the oar by changing oar.frame.up from (0,1,0) to (0,0,1). When oar.frame.up is in the (0,0,1) state, I notice that oar.frame.frame_to_world(oar.frame.up).y changes sign (and value) at specific transitions in oar.frame.axis. The effect is that my oar is suddenly 180 degrees out! Out of curiousity I traced oar.frame.world_to_frame(oar.frame.frame_to_world(oar.frame.up)) through the transitions, and it is always (0,0,1) [within rounding error]. Also curiously, when oar.frame.up is in the (0,1,0) state, I don't see the problem.

I realize that some disembodied diagnostics may be not always be useful, but I include them just in case. Main question is: what should I do next? If there's a chance of getting this fixed, I'm happy to provide some cut down source, or if there's a beta vpython build - happy to have a crack at building it and testing. Thanks for any help or advice. (I'm working on Yosemite with latest downloads installed as per vpython website instructions)

Correct (always):
StrokeState.DRIVE : new axis = <0.253956, 1.00912e-16, -0.967216> , up = <0, 1, 0> , up(world) = <0.5, 1.08, 0.8>
up (frame -> world -> frame) = <-6.4707e-18, 1, 2.81948e-17>

Correct:
StrokeState.RECOVERY : new axis = <0.216393, 1.02331e-16, -0.976306> , up = <0, 0, 1> , up(world) = <0.5, -0.92, 0.8>
up (frame -> world -> frame) = <-6.23294e-18, -2.81213e-17, 1>
Wrong:
StrokeState.RECOVERY : new axis = <0.178508, 1.03597e-16, -0.983939> , up = <0, 0, 1> , up(world) = <0.5, 1.08, 0.8>
up (frame -> world -> frame) = <-5.64189e-18, -1.02356e-18, 1>

Note change of sign and value for the oar.frame.up expressed in world coordinates at the transition from good to bad.

As I say, thanks for any help or advice (and have a great 2015, by the way).

Cheers

N.

Seems like characters are missing from your post, so that I see things like "up = ," that I don't know how to interpret. But as for the basic thrust, no, it has not been fixed and is a very old problem. My impression is that object.rotate() works okay but object.axis = vector has this problem. (And setting "up" to do what you want is probably very much to be avoided.) It has been suggested that the right way to deal with setting object.axis would involve using quaternions, but I don't know that's the case.

The VPython repository is https://github.com/BruceSherwood/vpython-wx, and in src/core/primitive.cpp and src/core/frame.cpp are the rotate and set_axis function for primitives (box, sphere, etc.) and frame objects. The rotate functions take some care to try not to cause problems, but the set_axis functions just set the axis, which obviously isn't adequate. It's conceivable that a fix just consists of modifying set_axis, something like this: (1) Let the current axis and the new axis define a plane (if they are co-linear, simply update the axis to capture a possible change of magnitude, and exit). (2) Create a normal to the plane, as a cross product of the two vectors. (3) Use the rotate function to carry out a rotation about the cross product vector through the angle between the current and new axis.

This could be tested by writing Python code to mimic these operations.

Hmm. I just tried following my own advice, and the following version of the original post does in fact work. Changing set_axis to work this way would obviously add some overhead to all operations to set the axis of an object, but it's in C++ so shouldn't be very expensive. I'll try it.

from visual import *

f = frame()
box() # marks frame origin (centre of rotation)

c = curve(frame=f, pos=[(0,0,0),(8,0,0),
(8,0,4),(8,2,4)], color=color.cyan)

def setaxis(f,axis):
a = f.axis
b = axis
c = cross(a,b)
ang = diff_angle(a,b)
f.rotate(angle=ang, axis=c)

##while True:
for angle in range(0,360):
rate(100)
angleR = radians(angle)
#f.axis = ( cos(angleR), sin(angleR), 0) # this fails
setaxis(f, vector( cos(angleR), sin(angleR), 0)) # this works
#f.rotate(angle=radians(1), axis=(0,0,1)) # this works
if f.frame_to_world( (0,0,10)).z < 0:
c.color = color.red
else:
c.color = color.cyan

Am I reading you right that you think you've found the issue? If so, I'll try copy your new python into mine and see what happens.

Regarding what you said about manipulating 'up' to be avoided. My task in modeling the rowing stroke is to rotate the oar about a vertical axis with an origin some way along its length (where it locates in the rigger). For half the stroke I need to have the oar rotated pi/2 about its own axis to model the feathering of the blade in the recovery. I did use rotate in an earlier iteration but it was giving the same flipping behaviour. I'll see if I can take your example above and apply it to my case.

FWIW these are the diagnostics from earlier (properly previewed and marked-down this time). In thinking about this, there's something else I'm not understanding. The oar is rotating in the x,z plane in world coordinates. So where oar.frame.up is (0,1,0), I'd expect this to be constant in world coordinates. But when oar.frame.up is (0,0,1), I'd expect it to be varying continuously in world coordinates as the oar itself rotates. Yet it's constant through the stroke, apart from the unexpected flip as shown below. And the model itself looks right, again, apart from the flip. Is this just my imperfect understanding of 'up'?

Anyway, the traces...

  • with up = (0,1,0) - no problems, no flip through the transition as I vary the frame axis ('new axis' in the diagnostic snippet here)
StrokeState.DRIVE : new axis =  <0.216393, 1.02331e-16, -0.976306> , up =  <0, 1, 0> , up(world) =  <0.5, 1.08, 0.8>
up (frame -> world -> frame) =  <-6.06082e-18, 1, 2.40245e-17>

StrokeState.DRIVE : new axis =  <0.178508, 1.03597e-16, -0.983939> , up =  <0, 1, 0> , up(world) =  <0.5, 1.08, 0.8>
up (frame -> world -> frame) =  <-5.64189e-18, 1, 1.98183e-17>
  • with up = (0,0,1) - note the (incorrect) change in the up vector in world coordinates, with the same values of the frame axis in both cases
StrokeState.RECOVERY : new axis =  <0.216393, 1.02331e-16, -0.976306> , up =  <0, 0, 1> , up(world) =  <0.5, -0.92, 0.8>
up (frame -> world -> frame) =  <-6.23294e-18, -2.81213e-17, 1>

StrokeState.RECOVERY : new axis =  <0.178508, 1.03597e-16, -0.983939> , up =  <0, 0, 1> , up(world) =  <0.5, 1.08, 0.8>
up (frame -> world -> frame) =  <-5.64189e-18, -1.02356e-18, 1>

I've successfully modified the set_axis functions in frame.cpp and primitives.cpp so that setting the axis seems to work properly. I hope to release VPython 6.11 soon, containing this fix. Until then, I won't attempt to diagnosis your other problems.

Ok thanks for the insight so far. Much appreciated.

Nick Johnson

On 26 Dec 2014, at 23:05, BruceSherwood notifications@github.com wrote:

I've successfully modified the set_axis functions in frame.cpp and primitives.cpp so that setting the axis seems to work properly. I hope to release VPython 6.11 soon, containing this fix. Until then, I won't attempt to diagnosis your other problems.


Reply to this email directly or view it on GitHub.

Bruce - I managed to solve my particular issue, though I’m not entirely sure how. I took your advice about avoiding manipulating frame.up and took more care about initialising frame.up before each rotation of frame.axis about itself, and it stopped the axis flipping I was observing. Not sure in the end whether I was uncovering a bug, or just programming it wrongly. Most likely the latter, I suspect. I’ll try it again with 6.11 when you issue it, and take it from there.

Thanks again for the insight, and have a great 2015

Best

N.

On Dec 26, 2014, at 11:05 PM, BruceSherwood notifications@github.com wrote:

I've successfully modified the set_axis functions in frame.cpp and primitives.cpp so that setting the axis seems to work properly. I hope to release VPython 6.11 soon, containing this fix. Until then, I won't attempt to diagnosis your other problems.


Reply to this email directly or view it on GitHub #55 (comment).

Fixed in 6.11: setting obj.axis now drives obj.rotate, which did not have the problem.