def absorbit(self, ref, axis, amt): """ Orbit the camera around an axis passing through a reference point ref. """ # Position the reference point in the proper plane with the camera # position. ref = ref[:axis] + (self.pos[axis],) + ref[axis + 1:] # Counter-rotate the camera. self.absrotate(axis=axis, amt=-1 * amt) delta = diff3D(ref, self.pos) r = dist3D(c=delta) # Cacheable: All these handled by setattr hook if axis == X_AXIS: theta = atan2(delta[Y_AXIS], delta[Z_AXIS]) theta = theta + (self.rotateSpeed * amt * pi / 180) self.pos = add3D(ref, (0, r * sin(theta), r * cos(theta))) elif axis == Y_AXIS: theta = atan2(delta[Z_AXIS], delta[X_AXIS]) theta = theta + (self.rotateSpeed * amt * pi / 180) self.pos = add3D(ref, (r * cos(theta), 0, r * sin(theta))) elif axis == Z_AXIS: theta = atan2(delta[X_AXIS], delta[Y_AXIS]) theta = theta + (-1 * self.rotateSpeed * amt * pi / 180) self.pos = add3D(ref, (r * sin(theta), r * cos(theta), 0))
def realPosition(self, camera): """ Figure out the world-space coordinates of this cursor relative to the camera given. """ from utilities import mult3D, add3D from camera import X_AXIS, Y_AXIS, Z_AXIS x = mult3D(camera.yz, self.pos[X_AXIS]) y = mult3D(camera.up, self.pos[Y_AXIS]) z = mult3D(camera.vpn, self.pos[Z_AXIS]) offset = add3D(x, y) offset = add3D(offset, z) return add3D(offset, camera.pos)
def vertexWarp(self, vertex, refresh=None): """ Warp to vertex, using the Animator. """ from utilities import add3D, diff3D, mult3D offset = add3D(mult3D(self.camera.vpn, -10.0), (0.0, vertex.radius * 3.0, 0.0)) if self.showGrid: npos = utilities.mult3D(vertex.pos, self.spacing) else: npos = vertex.pos targetpos = add3D(npos, offset) self.parent.animator.perspCameraMove(self.camera, newPos=targetpos, newLook=npos, duration=1.0)
def orbit(self, ref, axis, amt): """ Orbit the camera around an axis passing through a reerence point ref - this one orbits relative to the camera axes Currently, this function has the undesirable side-effect of possibly breaking the "camera.yz is parallel to the world Y=0 plane" constraint, and should not be used unless you know what you're doing. """ # Counter-rotate the camera. self.rotate(axis=axis, amt=amt) # Find the vector from the reference point to the camera vec = diff3D(ref, self.pos) # Rotate this vector around the axis if axis == X_AXIS: vec = rotate3D(vec, self.yz, amt) elif axis == Y_AXIS: vec = rotate3D(vec, self.up, amt) elif axis == Z_AXIS: vec = rotate3D(vec, self.vpn, -1 * amt) # Cacheable: Handled by setattr hook # Convert the vector into worldspace coordinates self.pos = add3D(ref, vec)
def useTracker(self): """ camera.useTracker() -> void Offsets the camera (should be called after use()) based on the current position of the tracker. """ if self.trackPosOffset: # Calculate the offset relative to the camera - rather than relative to the world. x = mult3D(self.yz, self.trackPosOffset[X_AXIS]) y = mult3D(self.up, self.trackPosOffset[Y_AXIS]) z = mult3D(self.vpn, self.trackPosOffset[Z_AXIS]) offset = add3D(x, y) offset = add3D(offset, z) GL.glTranslatef(*offset) if self.trackRotMatrix: GL.glMultMatrix(*self.trackRotMatrix)
def inputPos(self, pos): """ Translate an input device position into cursor coordinates. """ from utilities import add3D, diff3D, mult3D self.pos = add3D(pos, DEFAULT_POS)
def midpoint(self): """ Edge.midpoint() -> (float,float,float) Returns the midpoint of this edge as a position tuple. """ from utilities import add3D, div3D sum = add3D(self.source.pos, self.target.pos) return div3D(sum, 2.0)
def newValue(name, iter): """ This function gets called right now, and can return lambdas that evaluate in event time. """ if name in self._moveLock: return self._moveLock[name] elif isinstance(kwargs[name], (tuple, list)): return lambda: add3D(getattr(self._object, name), self._moveStepVals[name]) else: return lambda: getattr(self._object, name) + self._moveStepVals[name]
def arrangeStepwise(self, graph, vertices, newPos, duration, wait=0.0, tween=True): """ Arrange the vertices of the graph into a new layout specified by an indexed list of new positions newPos. The vertices will be positioned one at a time. """ assert (len(newPos) == len(vertices)) delay = float(duration) / len(vertices) from math import floor if tween: if delay > (1.0 / self.parent.fps): # No sense tweening faster than the frame rate can keep up stepsPerVert = int(floor(delay / (1.0 / self.parent.fps))) totalMove = [diff3D(a, b) for a, b in zip([x.pos for x in vertices], newPos)] partialMove = [div3D(x, stepsPerVert) for x in totalMove] else: tween = False accum = wait for i in range(len(vertices)): if tween: accum += delay / stepsPerVert for step in range(stepsPerVert - 1): self.post(accum, objects=( vertices[i], ), props=( ( 'pos', add3D(vertices[i].pos, mult3D(partialMove[i], (step + 1))), ), ), changeGraph=graph, ) accum += delay / stepsPerVert else: accum += delay self.post(accum, objects=( vertices[i], ), props=( ('pos', newPos[i]), ), changeGraph=graph, ) self.signal()
def draw(self): import graphgl # Calculate snap point = self.ctx.cursor.realPosition(self.ctx.camera) if self.relative: from utilities import add3D point = add3D(point, self.offset) newpos = tuple([x - ((x + self.snap / 2) % self.snap - (self.snap / 2)) for x in point]) self.vertex.pos = newpos self.ctx.graphgl.drawVertex(self.vertex, alpha=0.5) if self.updateHUD: self.ctx.HUDstatus = "Positioning Vertex %s: (%0.2f, %0.2f, %0.2f)" % ( self.vertex.id, self.vertex.pos[0], self.vertex.pos[1], self.vertex.pos[2])
def perspCameraLookatMove(self, camera, newPos, fixLook, duration, wait=0.0): """ Moves the camera smoothly to a new position and orientation. If newLook is None, does not change the orientation of the camera. If newPos is None, does not change the position of the camera. """ from camera import Camera, OrthoCamera, X_AXIS, Y_AXIS, Z_AXIS assert (not isinstance(camera, OrthoCamera)) fixLook = self.normalizePos(fixLook) steps = int(duration * self.parent.fps) deltaPos = diff3D(camera.pos, newPos) stepPos = div3D(deltaPos, steps) accum = wait delay = 1.0 / self.parent.fps # Set up the animation for i in range(steps - 1): accum += delay self.post(accum, objects=(camera,), props=( ( 'pos', add3D(camera.pos, mult3D(stepPos, i + 1)) ), ( 'lookAt', fixLook), ), call=( ('absrotate', (Y_AXIS, stepYaw), {}), ('rotate', (X_AXIS, stepPitch), {}), ), ) # Finish up in the correct place accum += delay self.post(accum, objects=(camera,), props=( ('pos', newPos), ('lookAt', fixLook), ), ) self.signal()
def arrangeMorph(self, graph, vertices, newPos, duration, wait=0.0): """ Arrange the vertices of the graph into a new layout specified by an indexed list of new positions newPos. The vertices will be positioned simultaneously (smooth morph) """ assert (len(newPos) == len(vertices)) accum = wait steps = int(duration * self.parent.fps) delay = 1.0 / self.parent.fps d = [div3D(diff3D(a.pos, b), steps) for a, b in zip(vertices, newPos)] for i in range(steps - 1): accum += delay self.post(accum, objects=vertices, multiprops=( ( 'pos', [add3D(a.pos, mult3D(b, i + 1)) for a, b in zip(vertices, d)] ), ), changeGraph=graph, ) accum += delay self.post(accum, objects=vertices, multiprops=( ('pos', newPos), ), changeGraph=graph, ) self.signal()
def arrangeSpring(context, axes=(X_AXIS, Z_AXIS, Y_AXIS), sync=None, edgeTension=0.85, edgeLength=3.0, useAttrEdgeK=False, invAttrEdgeK=False, useAttrEdgeLen=False, invAttrEdgeLen=False): """ Arrange the vertices of the graph according to a force-directed spring model. """ EPSILON = 0.1 JITTER = EPSILON / 8 TIMEINC = 0.7 from utilities import jitterForce, repulseForce, springForce, diff3D, add3D from graph import DummyVertex balanced = False # # Set vertex mass # # XXX# Dummy vertices are 1/4 the mass of regular vertices. vxMass = dict([(v.id, (pi / 3.0, 2.0 * pi / 3.0)[isinstance(v, DummyVertex)]) for v in context.graph.vertices]) #XXX# Scale by volume #vxMass = [4.0 * pi * v.radius * v.radius * v.radius / 3.0 for v in context.graph.vertices] # # Set vertex charge # vxCharge = dict([(v.id, 1.0) for v in context.graph.vertices]) # # Set edge spring tensions # # The edgeTension parameter is specified in the range [0.0, 1.0]. # Adjust it to be within reasonable bounds. if edgeTension > 1.0 or edgeTension < 0.0: raise GraphError, "Edge tension must be in the range [0.0, 1.0]" # 0.13 is practical (minimizing oscillations) # 0.11 is good (visibility separation) edgeTension = edgeTension * 0.15 if useAttrEdgeK: edgeK = context.graph.normalizeFromEdges( \ max=edgeTension, min=0.001, inv=invAttrEdgeK, \ valFunc=lambda e: float(e.attribute), \ failError="Some edge attributes could not be converted to float values for tension.") else: # not useAttrEdgeK edgeK = dict([((e.source.id, e.target.id), edgeTension) for e in context.graph.edges]) # # Set edge ideal length # # The edgeLength parameter must be >0.0 if edgeLength <= 0.0: raise GraphError, "Edge length must be >0.0" if useAttrEdgeLen: edgeLen = context.graph.normalizeFromEdges( \ max=edgeLength, min=0.0, inv=invAttrEdgeLen, \ valFunc=lambda e: float(e.attribute), \ failError="Some edge attributes could not be converted to float values for length.") else: edgeLen = dict([((e.source.id, e.target.id), edgeLength) for e in context.graph.edges]) # # Set pass number # I = 0 # Set changed flag now - if we set it after, then it's possible # that we could bail out without change flag getting set. context.graph.change() # # Do at least 2 passes to ensure that coincident vertices # get jittered apart. # while not balanced or I < 2: I = I + 1 # Initialize the force on each vertex to 0. vxForceSum = dict([(x.id, (0.0, 0.0, 0.0)) for x in context.graph.vertices]) balanced = True for v_num in range(len(context.graph.vertices)): v = context.graph.vertices[v_num] # Edges for u in v.nearestVertices(): if u == v: continue edges = context.graph.findEdgeBetween(u, v) for e in edges: F = springForce(diff3D(v.pos, u.pos), Kuv=edgeK[(e.source.id, e.target.id)], Luv=edgeLen[(e.source.id, e.target.id)], j=JITTER) vxForceSum[v.id] = add3D(vxForceSum[v.id], F) # Vertices for u in context.graph.vertices: if u == v: continue F = repulseForce(diff3D(u.pos, v.pos), q1=vxCharge[u.id], q2=vxCharge[v.id], Cuv=300, j=JITTER) vxForceSum[v.id] = add3D(vxForceSum[v.id], F) # Jitter F = jitterForce(JITTER) vxForceSum[v.id] = add3D(vxForceSum[v.id], F) # Tether F = springForce(diff3D(v.pos, (0.0, 0.0, 0.0)), Kuv=0.005, Luv=1.0, j=JITTER) vxForceSum[v.id] = add3D(vxForceSum[v.id], F) if abs(vxForceSum[v.id][0]) > EPSILON and X_AXIS in axes or \ abs(vxForceSum[v.id][1]) > EPSILON and Y_AXIS in axes or \ abs(vxForceSum[v.id][2]) > EPSILON and Z_AXIS in axes: balanced = False # Now we have the force on each vertex. # Act on them. for v in context.graph.vertices: vel = ((X_AXIS in axes) * vxForceSum[v.id][0] * TIMEINC / vxMass[v.id], (Y_AXIS in axes) * vxForceSum[v.id][1] * TIMEINC / vxMass[v.id], (Z_AXIS in axes) * vxForceSum[v.id][2] * TIMEINC / vxMass[v.id]) v.pos = add3D(v.pos, vel) if sync: # Because we're saving calls by referring to the vertices directly, # we have to set the change flag here, before we sync. context.graph.change() sync() context.graph.change() return I
def perspCameraMove(self, camera, newPos=None, newLook=None, duration=1.0, wait=0.0): """ Moves the camera smoothly to a new position and orientation. If newLook is None, does not change the orientation of the camera. If newPos is None, does not change the position of the camera. """ from camera import Camera, OrthoCamera, X_AXIS, Y_AXIS, Z_AXIS assert (not isinstance(camera, OrthoCamera)) newCam = Camera() if newPos != None: newCam.pos = self.normalizePos(newPos) else: newCam.pos = camera.pos if newLook != None: newCam.lookAt = self.normalizePos(newLook) else: newCam.lookAt = camera.lookAt steps = int(duration * self.parent.fps) (oldTheta, oldPhi) = camera.polar() (newTheta, newPhi) = newCam.polar() deltaYaw = newTheta - oldTheta deltaPitch = newPhi - oldPhi deltaPos = diff3D(camera.pos, newCam.pos) # Check to make sure we're coming around the short side if deltaYaw > 180.0: deltaYaw = deltaYaw - 360.0 elif deltaYaw < -180.0: deltaYaw = deltaYaw + 360.0 print "DeltaYaw", deltaYaw stepYaw = deltaYaw / steps stepPitch = deltaPitch / steps stepPos = div3D(deltaPos, steps) accum = wait delay = 1.0 / self.parent.fps # Set up the animation for i in range(steps - 1): accum += delay self.post(accum, objects=( camera, ), props=( ( 'pos', add3D(camera.pos, mult3D(stepPos, i + 1)), ), ), call=( ('absrotate', (Y_AXIS, stepYaw), {}), ('rotate', (X_AXIS, stepPitch), {}), ), ) # Finish up in the correct place accum += delay self.post(accum, objects=(camera,), props=( ('pos', newCam.pos), ('lookAt', newCam.lookAt), ), ) self.signal()