I am attempting to create a grid of locators that serve as projected points onto a parallel finite plane from a camera in maya at a specified depth. The grid should line up with a specified resolution so as to match rendered output.
At the moment my calculations are off and I am looking for some help to ascertain how my formula for ascertaining the projected points is incorrect.
I have a self contained python script and image showing the current position of locators that are spawned as an example.
image showing current spawned locators are off on y and z axis
import maya.cmds as mc
import maya.OpenMaya as om
res = [mc.getAttr('defaultResolution.width'),
mc.getAttr('defaultResolution.height')]
print res
grid = [5, 5]
def projectedGridPoint(camera, coord, depth, res):
selList = om.MSelectionList()
selList.add(camera)
dagPath = om.MDagPath()
selList.getDagPath(0,dagPath)
dagPath.extendToShape()
camMtx = dagPath.inclusiveMatrix()
fnCam = om.MFnCamera(dagPath)
mFloatMtx = fnCam.projectionMatrix()
projMtx = om.MMatrix(mFloatMtx.matrix)
#center of camera
eyePt = fnCam.eyePoint()
#offset position
z = eyePt.z - depth
#calculated xy positions
x = (2 * z * coord[0] / res[0]) - z
y = (2 * z * coord[1] / res[1]) - z
return om.MPoint(x,y,depth) * camMtx * projMtx.inverse()
for y in range(grid[1] + 1):
for x in range(grid[0] + 1):
coord = ( x / float(grid[0]) * res[0], y / float(grid[1]) * res[1] )
pt = projectedGridPoint('camera1', coord, 10, res)
mc.spaceLocator(a=1, p=[pt.x, pt.y, pt.z])
Once I adjusted Theodox's answer to account for all possible grid divisions, such that the ndc_x and ndc_y was always in the range of -1 and 1. I was able to get a working solution.
import maya.api.OpenMaya as om
import maya.cmds as cmds
def projectedGridPoint(camera, coord, depth):
selList = om.MGlobal.getSelectionListByName(camera)
dagPath = selList.getDagPath(0)
dagPath.extendToShape()
view = dagPath.inclusiveMatrix()
fnCam = om.MFnCamera(dagPath)
projection = om.MMatrix(fnCam.projectionMatrix())
viewProj = projection * view
r = om.MPoint(coord[0],coord[1], -1 * depth) * projection.inverse()
return r.homogenize() * view
xx, yy = (6, 6)
for y in range(yy + 1):
for x in range(xx + 1):
ndc_x = -1
ndc_y = -1
if x > 0:
ndc_x = (x / float(xx) * 2) - 1
if y > 0:
ndc_y = (y / float(yy) * 2) - 1
coord = ( ndc_x, ndc_y)
print coord
pt = projectedGridPoint('camera1', coord, 0)
c,_ = cmds.polyCube(w = 0.1, d = 0.1, h = 0.1)
cmds.xform(c, t = (pt[0], pt[1], pt[2]))
I think you want something a bit more like this (note, i converted it to API 2 to cut down on the boilerplate)
import maya.api.OpenMaya as om
import maya.cmds as cmds
def projectedGridPoint(camera, coord, depth):
selList = om.MGlobal.getSelectionListByName(camera)
dagPath = selList.getDagPath(0)
dagPath.extendToShape()
view = dagPath.inclusiveMatrix()
fnCam = om.MFnCamera(dagPath)
projection = om.MMatrix(fnCam.projectionMatrix())
viewProj = projection * view
r = om.MPoint(coord[0],coord[1], -1 * depth) * projection.inverse()
return r.homogenize() * view
xx, yy = (2, 2)
for y in range(yy):
for x in range(xx):
ndc_x = 2.0 * x / float(xx - 1) - 1
ndc_y = 2.0 * y / float(yy - 1) - 1
coord = ( ndc_x, ndc_y)
pt = projectedGridPoint('camera1', coord,0)
c,_ = cmds.polyCube(w = 0.1, d = 0.1, h = 0.1)
cmds.xform(c, t = (pt[0], pt[1], pt[2]))
The coords are supplied as normalized device coordinates (from -1,-1 to 1, 1 at the corners of the view) and the depth goes from the near to far clip planes -- a depth of 1 is right on the near plane and a depth of 0 is on the far plane. I think in practice I'd lock the depth at 0 and use the clip plane setting on the camera to set the depth
edit I rationalized the original, wonky method of converting index values to NDC coordinates
Related
I'm attempting to implement the algorithm for generating 2D Perlin noise here but I'm having some trouble doing it in Python (which I am relatively new to).
I was expecting the final noise values ('z' in the linked example), to be somewhere between 0.0 and 1.0, but that's not what I'm getting. My code is below, I'd really appreciate any input.
Thanks!
perlin.py:
import math
import numpy
import random
import vector as vctr
from PIL import Image
def dot(v1, v2):
"""
Returns the dot product of the two input vectors.
Args:
v1 - First vector
v2 - Second vector
Return:
Resulting dot product
"""
return (v1.x * v2.x) + (v1.y * v2.y)
def fade(t):
"""
Fade 3t^2 - 2t^3
Args:
t - Value to fade.
Return:
Faded value.
"""
return (3 * (t ** 2)) - (2 * (t ** 3))
def lerp(minVal, maxVal, term):
"""
Args:
Return:
"""
return (maxVal - minVal) * term + minVal
def generateImage(noises, file="perlin.png"):
"""
Generates a image on disc of the resulting noise values
Args:
noises (list) - 2d list of noise values
file (str) - location of file to write to
"""
pixels = numpy.zeros((height, width, 3), dtype=numpy.uint8)
for x in range(0, width):
for y in range(0, height):
rgb = 255 * noises[x][y]
pixels[x, y] = [rgb, rgb, rgb]
# Print pixels as image
img = Image.fromarray(pixels, 'RGB')
img.save(file)
# Define the noise region
width = 300
height = 300
# Column ordered array of generated gradient vectors
g = numpy.zeros((width + 1, height + 1)).tolist()
# List of final noise values
z = numpy.zeros((width, height)).tolist()
# Fill list with randomly directed unit vectors (one for each grid point)
for x in range(0, width + 1):
for y in range(0, height + 1):
randX = random.uniform(-1.0, 1.0)
randY = random.uniform(-1.0, 1.0)
length = math.sqrt(randX**2 + randY**2)
g[x][y] = vctr.vector(randX / length, randY / length)
# For each cell in the sampling space (i.e. each pixel)
for x in range(0, width):
for y in range(0, height):
# Generate random point (p) within and relative to current cell
pX = random.uniform(0.0, 1.0)
pY = random.uniform(0.0, 1.0)
# Get the gradient vectors for each cell corner
g_tl = g[x][y]
g_tr = g[x + 1][y]
g_bl = g[x][y + 1]
g_br = g[x + 1][y + 1]
# Vectors from each cell corner to the generated point
# X axis is positive going right, Y is positive going down
tl = vctr.vector(pX, pY)
tr = vctr.vector(pX - 1, pY)
bl = vctr.vector(pX, pY - 1)
br = vctr.vector(pX - 1, pY - 1)
# Dot product these vectors to get gradient values
u = dot(tl, g_tl)
v = dot(tr, g_tr)
s = dot(bl, g_bl)
t = dot(br, g_br)
# Interpolate the gradient values
sX = fade(pX)
sY = fade(pY)
a = s + (sX * (t - s))
b = u + (sX * (v - u))
value = a + (sY * (a - b))
if (value < 0.0) or (value > 1.0):
print("VALUE IS OUT OF BOUNDS??? " + str(value))
z[x][y] = value
generateImage(z)
print("Completed Perlin noise generation!")
vector.py:
class vector:
def __init__(self, x, y):
"""
Initialise a new vector in 2D space with the input X and Y values.
x: X value of vector
y: Y value of vector
"""
self.x = x
self.y = y
Is this the best way to go about placing an object along the vector created by two other objects in the scene? I hope you guys can help me make this more efficient as it seems very redundant and long for such a simple concept.
Thanks
import maya.cmds as cmds
import random
cmds.select(all=True)
cmds.delete()
#------------------------------TEST SCENE SETUP
def genPos():
x = random.uniform(-5,5)
y = random.uniform(0,5)
z = random.uniform(-5,5)
return (x, y, z)
a = cmds.spaceLocator(n='ctrl_00')
b = cmds.spaceLocator(n='ctrl_00')
cmds.xform(a, t=(genPos()) )
cmds.xform(b, t=(genPos()) )
cmds.createDisplayLayer(name="Ctrls")
cmds.editDisplayLayerMembers('Ctrls', a, b)
cmds.setAttr('Ctrls.color' ,14)
cmds.select(clear=True)
#-----------------------THE SCRIPT
def normlizedVector(vecA,vecB,offset):
nX = vecB[0] - vecA[0]
nY = vecB[1] - vecA[1]
nZ = vecB[2] - vecA[2]
#vectorLength = distance vecA vecB
# find the distance between the two supplied point3 values
distX = pow( (vecA[0] - vecB[0] ) , 2.0 )
distY = pow( (vecA[1] - vecB[1] ) , 2.0 )
distZ = pow( (vecA[2] - vecB[2] ) , 2.0 )
vecLength = sqrt(distX + distY + distZ)
# the normalized vector is calculated by dividing the X, Y and Z coordinates by the length
calcX = nX / vecLength
calcY = nY / vecLength
calcZ = nZ / vecLength
# project point along vector, offset by a given value
ptX = vecB[0] + (calcX * offset)
ptY = vecB[1] + (calcY * offset)
ptZ = vecB[2] + (calcZ * offset)
return (ptX, ptY, ptZ)
posA = cmds.xform(a,q=1,ws=1,rp=1)
posB = cmds.xform(b,q=1,ws=1,rp=1)
pt = normlizedVector(posA,posB,10)
c = cmds.spaceLocator(n='main')
cmds.xform(c, t=(pt) )
posC = cmds.xform(c,q=1,ws=1,rp=1)
cmds.distanceDimension( sp=posB, ep=posC )
Is there a way to draw direction fields in python?
My attempt is to modify http://www.compdigitec.com/labs/files/slopefields.py giving
#!/usr/bin/python
import math
from subprocess import CalledProcessError, call, check_call
def dy_dx(x, y):
try:
# declare your dy/dx here:
return x**2-x-2
except ZeroDivisionError:
return 1000.0
# Adjust window parameters
XMIN = -5.0
XMAX = 5.0
YMIN = -10.0
YMAX = 10.0
XSCL = 0.5
YSCL = 0.5
DISTANCE = 0.1
def main():
fileobj = open("data.txt", "w")
for x1 in xrange(int(XMIN / XSCL), int(XMAX / XSCL)):
for y1 in xrange(int(YMIN / YSCL), int(YMAX / YSCL)):
x= float(x1 * XSCL)
y= float(y1 * YSCL)
slope = dy_dx(x,y)
dx = math.sqrt( DISTANCE/( 1+math.pow(slope,2) ) )
dy = slope*dx
fileobj.write(str(x) + " " + str(y) + " " + str(dx) + " " + str(dy) + "\n")
fileobj.close()
try:
check_call(["gnuplot","-e","set terminal png size 800,600 enhanced font \"Arial,12\"; set xrange [" + str(XMIN) + ":" + str(XMAX) + "]; set yrange [" + str(YMIN) + ":" + str(YMAX) + "]; set output 'output.png'; plot 'data.txt' using 1:2:3:4 with vectors"])
except (CalledProcessError, OSError):
print "Error: gnuplot not found on system!"
exit(1)
print "Saved image to output.png"
call(["xdg-open","output.png"])
return 0
if __name__ == '__main__':
main()
However the best image I get from this is.
How can I get an output that looks more like the first image? Also, how can I add the three solid lines?
You can use this matplotlib code as a base. Modify it for your needs.
I have updated the code to show same length arrows. The important option is to set the angles option of the quiver function, so that the arrows are correctly printed from (x,y) to (x+u,y+v) (instead of the default, which just takes into account of (u,v) when computing the angles).
It is also possible to change the axis form "boxes" to "arrows". Let me know if you need that change and I could add it.
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import numpy as np
fig = plt.figure()
def vf(x, t):
dx = np.zeros(2)
dx[0] = 1.0
dx[1] = x[0] ** 2 - x[0] - 2.0
return dx
# Solution curves
t0 = 0.0
tEnd = 10.0
# Vector field
X, Y = np.meshgrid(np.linspace(-5, 5, 20), np.linspace(-10, 10, 20))
U = 1.0
V = X ** 2 - X - 2
# Normalize arrows
N = np.sqrt(U ** 2 + V ** 2)
U = U / N
V = V / N
plt.quiver(X, Y, U, V, angles="xy")
t = np.linspace(t0, tEnd, 100)
for y0 in np.linspace(-5.0, 0.0, 10):
y_initial = [y0, -10.0]
y = odeint(vf, y_initial, t)
plt.plot(y[:, 0], y[:, 1], "-")
plt.xlim([-5, 5])
plt.ylim([-10, 10])
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
I had a lot of fun making one of these as a hobby project using pygame. I plotted the slope at each pixel, using shades of blue for positive and shades of red for negative. Black is for undefined. This is dy/dx = log(sin(x/y)+cos(y/x)):
You can zoom in & out - here is zoomed in on the middle upper part here:
and also click on a point to graph the line going through that point:
It's just 440 lines of code, so here is the .zip of all the files. I guess I'll excerpt relevant bits here.
The equation itself is input as a valid Python expression in a string, e.g. "log(sin(x/y)+cos(y/x))". This is then compiled. This function here graphs the color field, where self.func.eval() gives the dy/dx at the given point. The code is a bit complicated here because I made it render in stages - first 32x32 blocks, then 16x16, etc. - to make it snappier for the user.
def graphcolorfield(self, sqsizes=[32,16,8,4,2,1]):
su = ScreenUpdater(50)
lastskip = self.xscreensize
quitit = False
for squaresize in sqsizes:
xsquaresize = squaresize
ysquaresize = squaresize
if squaresize == 1:
self.screen.lock()
y = 0
while y <= self.yscreensize:
x = 0
skiprow = y%lastskip == 0
while x <= self.xscreensize:
if skiprow and x%lastskip==0:
x += squaresize
continue
color = (255,255,255)
try:
m = self.func.eval(*self.ct.untranscoord(x, y))
if m >= 0:
if m < 1:
c = 255 * m
color = (0, 0, c)
else:
#c = 255 - 255 * (1.0/m)
#color = (c, c, 255)
c = 255 - 255 * (1.0/m)
color = (c/2.0, c/2.0, 255)
else:
pm = -m
if pm < 1:
c = 255 * pm
color = (c, 0, 0)
else:
c = 255 - 255 * (1.0/pm)
color = (255, c/2.0, c/2.0)
except:
color = (0, 0, 0)
if squaresize > 1:
self.screen.fill(color, (x, y, squaresize, squaresize))
else:
self.screen.set_at((x, y), color)
if su.update():
quitit = True
break
x += xsquaresize
if quitit:
break
y += ysquaresize
if squaresize == 1:
self.screen.unlock()
lastskip = squaresize
if quitit:
break
This is the code which graphs a line through a point:
def _grapheqhelp(self, sx, sy, stepsize, numsteps, color):
x = sx
y = sy
i = 0
pygame.draw.line(self.screen, color, (x, y), (x, y), 2)
while i < numsteps:
lastx = x
lasty = y
try:
m = self.func.eval(x, y)
except:
return
x += stepsize
y = y + m * stepsize
screenx1, screeny1 = self.ct.transcoord(lastx, lasty)
screenx2, screeny2 = self.ct.transcoord(x, y)
#print "(%f, %f)-(%f, %f)" % (screenx1, screeny1, screenx2, screeny2)
try:
pygame.draw.line(self.screen, color,
(screenx1, screeny1),
(screenx2, screeny2), 2)
except:
return
i += 1
stx, sty = self.ct.transcoord(sx, sy)
pygame.draw.circle(self.screen, color, (int(stx), int(sty)), 3, 0)
And it runs backwards & forwards starting from that point:
def graphequation(self, sx, sy, stepsize=.01, color=(255, 255, 127)):
"""Graph the differential equation, given the starting point sx and sy, for length
length using stepsize stepsize."""
numstepsf = (self.xrange[1] - sx) / stepsize
numstepsb = (sx - self.xrange[0]) / stepsize
self._grapheqhelp(sx, sy, stepsize, numstepsf, color)
self._grapheqhelp(sx, sy, -stepsize, numstepsb, color)
I never got around to drawing actual lines because the pixel approach looked too cool.
Try changing your values for the parameters to this:
XSCL = .2
YSCL = .2
These parameters determine how many points are sampled on the axes.
As per your comment, you'll need to also plot the functions for which the derivation dy_dx(x, y) applies.
Currently, you're only calculating and plotting the slope lines as calculated by your function dy_dx(x,y). You'll need to find (in this case 3) functions to plot in addition to the slope.
Start by defining a function:
def f1_x(x):
return x**3-x**2-2x;
and then, in your loop, you'll have to also write the desired values for the functions into the fileobj file.
I have this programme to discuss and I think its a challenging one.. Here I have a yml file which contains the data for an image. The image has x,y,z values and intensity data which is stored in this yml file. I have used opencv to load the data and its working fine with masking.. but I am having problems in dynamically appending the masks created.. Here is the code I made for solving the problem :
import cv
from math import floor, sqrt, ceil
from numpy import array, dot, subtract, add, linalg as lin
mask_size = 9
mask_size2 = mask_size / 2
f = open("Classified_Image1.txt", "w")
def distance(centre, point):
''' To find out the distance between centre and the point '''
dist = sqrt(
((centre[0]-point[0])**2) +
((centre[1]-point[1])**2) +
((centre[2]-point[2])**2)
)
return dist
def CalcCentre(points): # Calculates centre for a given set of points
centre = array([0,0,0])
count = 0
for p in points:
centre = add(centre, array(p[:3]))
count += 1
centre = dot(1./count, centre)
print centre
return centre
def addrow(data, points, x, y, ix , iy ):# adds row to the mask
iy = y + 1
for dx in xrange(-mask_size2 , mask_size2 + 2):
ix = x + dx
rowpoints = addpoints(data, points, iy, ix)
return rowpoints
def addcolumn(data, points, x, y, ix , iy ):# adds column to the mask
ix = x + 1
for dy in xrange(-mask_size2-1 , mask_size2 + 1):
iy = y + dy
columnpoints = addpoints(data, points, iy, ix)
return columnpoints
def addpoints (data, points, iy, ix): # adds a list of relevant points
if 0 < ix < data.width and 0 < iy < data.height:
pnt = data[iy, ix]
if pnt != (0.0, 0.0, 0.0):
print ix, iy
print pnt
points.append(pnt)
return points
def CreateMask(data, y, x):
radius = 0.3
points = []
for dy in xrange(-mask_size2, mask_size2 + 1): ''' Masking the data '''
for dx in xrange(-mask_size2, mask_size2 + 1):
ix, iy = x + dx, y + dy
points = addpoints(data, points, iy , ix )
if len(points) > 3:
centre = CalcCentre(points)
distances = []
for point in points :
dist = distance(centre, point)
distances.append(dist)
distancemax = max(distances)
print distancemax
if distancemax < radius: ''' Dynamic Part of the Programme'''
#while dist < radius: # Going into infinite loop .. why ?
p = addrow(data, points, x, y, ix , iy )
q = addcolumn(data, points, x, y, ix , iy )
dist = distance(centre, point) # While should not go in infinite
#loop as dist is changing here
print dist
print len(p), p
print len(q), q
points = p + q
points = list(set(points)) # To remove duplicate points in the list
print len(points), points
def ComputeClasses(data):
for y in range(0, data.height):
for x in range(0, data.width):
CreateMask(data, y, x)
if __name__ == "__main__":
data = cv.Load("Z:/data/xyz_00000_300.yml")
print "data loaded"
ComputeClasses(data)
Feel free to suggest alternative methods/ideas to solve this problem.
Thanks in advance.
Hi :)
i have the following python code that generates points lying on a sphere's surface
from math import sin, cos, pi
toRad = pi / 180
ox = 10
oy = -10
oz = 50
radius = 10.0
radBump = 3.0
angleMin = 0
angleMax = 360
angleOffset = angleMin * toRad
angleRange = (angleMax - angleMin) * toRad
steps = 48
angleStep = angleRange / steps
latMin = 0
latMax = 180
latOffset = latMin * toRad
if (latOffset < 0):
latOffset = 0;
latRange = (latMax - latMin) * toRad
if (latRange > pi):
latRange = pi - latOffset;
latSteps = 48
latAngleStep = latRange / latSteps
for lat in range(0, latSteps):
ang = lat * latAngleStep + latOffset
z = cos(ang) * radius + oz
radMod = sin(ang) * radius
for a in range(0, steps):
x = sin(a * angleStep + angleOffset) * radMod + ox
y = cos(a * angleStep + angleOffset) * radMod + oy
print "%f %f %f"%(x,y,z)
after that i plot the points with gnuplot using splot 'datafile'
can you give any hints on how to create deformations on that sphere?
like "mountains" or "spikes" on it?
(something like the openbsd logo ;) : https://https.openbsd.org/images/tshirt-23.gif )
i know it is a trivial question :( but thanks for your time :)
DsP
The approach that springs to my mind, especially with the way you compute a set of points that are not explicitly connected, is to find where the point goes on the sphere's surface, then move it by a distance and direction determined by a set of control points. The control points could have smaller effects the further away they are. For example:
# we have already computed a points position on the sphere, and
# called it x,y,z
for p in controlPoints:
dx = p.x - x
dy = p.y - y
dz = p.z - z
xDisplace += 1/(dx*dx)
yDisplace += 1/(dy*dy)
zDisplace += 1/(dz*dz) # using distance^2 displacement
x += xDisplace
y += yDisplace
z += zDisplace
By changing the control points you can alter the sphere's shape
By changing the movement function, you can alter the way the points shape the sphere
You could get really tricky and have different functions for different points:
# we have already computed a points position on the sphere, and
# called it x,y,z
for p in controlPoints:
xDisplace += p.displacementFunction(x)
yDisplace += p.displacementFunction(y)
zDisplace += p.displacementFunction(z)
x += xDisplace
y += yDisplace
z += zDisplace
If you do not want all control points affecting every point in the sphere, just build that into the displacement function.
How's this?
from math import sin, cos, pi, radians, ceil
import itertools
try:
rng = xrange # Python 2.x
except NameError:
rng = range # Python 3.x
# for the following calculations,
# - all angles are in radians (unless otherwise specified)
# - latitude is in [-pi/2..pi/2]
# - longitude is in [-pi..pi)
MIN_LAT = -pi/2 # South Pole
MAX_LAT = pi/2 # North Pole
MIN_LON = -pi # Far West
MAX_LON = pi # Far East
def floatRange(start, end=None, step=1.0):
"Floating-point range generator"
start += 0.0 # cast to float
if end is None:
end = start
start = 0.0
steps = int(ceil((end-start)/step))
return (start + k*step for k in rng(0, steps+1))
def patch2d(xmin, xmax, ymin, ymax, step=1.0):
"2d rectangular grid generator"
if xmin>xmax:
xmin,xmax = xmax,xmin
xrange = floatRange(xmin, xmax, step)
if ymin>ymax:
ymin,ymax = ymax,ymin
yrange = floatRange(ymin, ymax, step)
return itertools.product(xrange, yrange)
def patch2d_to_3d(xyIter, zFn):
"Convert 2d field to 2.5d height-field"
mapFn = lambda a: (a[0], a[1], zFn(a[0],a[1]))
return itertools.imap(mapFn, xyIter)
#
# Representation conversion functions
#
def to_spherical(lon, lat, rad):
"Map from spherical to spherical coordinates (identity function)"
return lon, lat, rad
def to_cylindrical(lon, lat, rad):
"Map from spherical to cylindrical coordinates"
# angle, z, radius
return lon, rad*sin(lat), rad*cos(lat)
def to_cartesian(lon, lat, rad):
"Map from spherical to Cartesian coordinates"
# x, y, z
cos_lat = cos(lat)
return rad*cos_lat*cos(lon), rad*cos_lat*sin(lon), rad*sin(lat)
def bumpySphere(gridSize, radiusFn, outConv):
lonlat = patch2d(MIN_LON, MAX_LON, MIN_LAT, MAX_LAT, gridSize)
return list(outConv(*lonlatrad) for lonlatrad in patch2d_to_3d(lonlat, radiusFn))
# make a plain sphere of radius 10
sphere = bumpySphere(radians(5.0), lambda x,y: 10.0, to_cartesian)
# spiky-star-function maker
def starFnMaker(xWidth, xOffset, yWidth, yOffset, minRad, maxRad):
# make a spiky-star function:
# longitudinal and latitudinal triangular waveforms,
# joined as boolean intersection,
# resulting in a grid of positive square pyramids
def starFn(x, y, xWidth=xWidth, xOffset=xOffset, yWidth=yWidth, yOffset=yOffset, minRad=minRad, maxRad=maxRad):
xo = ((x-xOffset)/float(xWidth)) % 1.0 # xo in [0.0..1.0), progress across a single pattern-repetition
xh = 2 * min(xo, 1.0-xo) # height at xo in [0.0..1.0]
xHeight = minRad + xh*(maxRad-minRad)
yo = ((y-yOffset)/float(yWidth)) % 1.0
yh = 2 * min(yo, 1.0-yo)
yHeight = minRad + yh*(maxRad-minRad)
return min(xHeight, yHeight)
return starFn
# parameters to spike-star-function maker
width = 2*pi
horDivs = 20 # number of waveforms longitudinally
horShift = 0.0 # longitudinal offset in [0.0..1.0) of a wave
height = pi
verDivs = 10
verShift = 0.5 # leave spikes at the poles
minRad = 10.0
maxRad = 15.0
deathstarFn = starFnMaker(width/horDivs, width*horShift/horDivs, height/verDivs, height*verShift/verDivs, minRad, maxRad)
deathstar = bumpySphere(radians(2.0), deathstarFn, to_cartesian)
so i finally created the deformation using a set of control points that "pull" the spherical
surface. it is heavilly OO and ugly though ;)
thanks for all the help !!!
to use it > afile and with gnuplot : splot 'afile' w l
DsP
from math import sin, cos, pi ,sqrt,exp
class Point:
"""a 3d point class"""
def __init__(self,x,y,z):
self.x = x
self.y = y
self.z = z
def __repr__(self):
return "%f %f %f\n"%(self.x,self.y,self.z)
def __str__(self):
return "point centered: %f %f %f\n"%(self.x,self.y,self.z)
def distance(self,b):
return sqrt((self.x - b.x)**2 +(self.y - b.y)**2 +(self.z -b.z)**2)
def displaceTowards(self,b):
self.x
class ControlPoint(Point):
"""a control point that deforms positions of other points"""
def __init__(self,p):
Point.__init__(self,p.x,p.y,p.z)
self.deformspoints=[]
def deforms(self,p):
self.deformspoints.append(p)
def deformothers(self):
self.deformspoints.sort()
#print self.deformspoints
for i in range(0,len(self.deformspoints)):
self.deformspoints[i].x += (self.x - self.deformspoints[i].x)/2
self.deformspoints[i].y += (self.y - self.deformspoints[i].y)/2
self.deformspoints[i].z += (self.z - self.deformspoints[i].z)/2
class Sphere:
"""returns points on a sphere"""
def __init__(self,radius,angleMin,angleMax,latMin,latMax,discrStep,ox,oy,oz):
toRad = pi/180
self.ox=ox
self.oy=oy
self.oz=oz
self.radius=radius
self.angleMin=angleMin
self.angleMax=angleMax
self.latMin=latMin
self.latMax=latMax
self.discrStep=discrStep
self.angleRange = (self.angleMax - self.angleMin)*toRad
self.angleOffset = self.angleMin*toRad
self.angleStep = self.angleRange / self.discrStep
self.latOffset = self.latMin*toRad
self.latRange = (self.latMax - self.latMin) * toRad
self.latAngleStep = self.latRange / self.discrStep
if(self.latOffset <0):
self.latOffset = 0
if(self.latRange > pi):
self.latRange = pi - latOffset
def CartesianPoints(self):
PointList = []
for lat in range(0,self.discrStep):
ang = lat * self.latAngleStep + self.latOffset
z = cos(ang) * self.radius + self.oz
radMod = sin(ang)*self.radius
for a in range(0,self.discrStep):
x = sin(a*self.angleStep+self.angleOffset)*radMod+self.ox
y = cos(a*self.angleStep+self.angleOffset)*radMod+self.oy
PointList.append(Point(x,y,z))
return PointList
mysphere = Sphere(10.0,0,360,0,180,50,10,10,10)
mylist = mysphere.CartesianPoints()
cpoints = [ControlPoint(Point(0.0,0.0,0.0)),ControlPoint(Point(20.0,0.0,0.0))]
deforpoints=[]
for cp in cpoints:
for p in mylist:
if(p.distance(cp) < 15.0):
cp.deforms(p)
"""print "cp ",cp,"deforms:"
for dp in cp.deformspoints:
print dp ,"at distance", dp.distance(cp)"""
cp.deformothers()
out= mylist.__repr__()
s = out.replace(","," ")
print s