def raytrace_schedule(i,schedule,total_shared,q): # this is the function running on each thread #global schedules,itcounters,chnkcounters,killers if len(schedule) == 0: return total_colour_buffer_preproc = tonumpyarray(total_shared) #schedule = schedules[i] itcounters[i] = 0 chnkcounters[i]= 0 for chunk in schedule: #if killers[i]: # break chnkcounters[i]+=1 #number of chunk pixels numChunk = chunk.shape[0] #useful constant arrays ones = np.ones((numChunk)) ones3 = np.ones((numChunk,3)) UPFIELD = np.outer(ones,np.array([0.,1.,0.])) BLACK = np.outer(ones,np.array([0.,0.,0.])) #arrays of integer pixel coordinates x = chunk % RESOLUTION[0] y = chunk / RESOLUTION[0] showprogress("Generating view vectors...",i,q) #the view vector in 3D space view = np.zeros((numChunk,3)) view[:,0] = x.astype(float)/RESOLUTION[0] - .5 view[:,1] = ((-y.astype(float)/RESOLUTION[1] + .5)*RESOLUTION[1])/RESOLUTION[0] #(inverting y coordinate) view[:,2] = 1.0 view[:,0]*=TANFOV view[:,1]*=TANFOV #rotating through the view matrix view = np.einsum('jk,ik->ij',viewMatrix,view) #original position point = np.outer(ones, CAMERA_POS) normview = normalize(view) velocity = np.copy(normview) # initializing the colour buffer object_colour = np.zeros((numChunk,3)) object_alpha = np.zeros(numChunk) #squared angular momentum per unit mass (in the "Newtonian fantasy") #h2 = np.outer(sqrnorm(np.cross(point,velocity)),np.array([1.,1.,1.])) h2 = sqrnorm(np.cross(point,velocity))[:,np.newaxis] pointsqr = np.copy(ones3) for it in range(NITER): itcounters[i]+=1 if it%150 == 1: if killers[i]: break showprogress("Raytracing...",i,q) # STEPPING oldpoint = np.copy(point) #not needed for tracing. Useful for intersections if METHOD == METH_LEAPFROG: #leapfrog method here feels good point += velocity * STEP if DISTORT: #this is the magical - 3/2 r^(-5) potential... accel = - 1.5 * h2 * point / np.power(sqrnorm(point),2.5)[:,np.newaxis] velocity += accel * STEP elif METHOD == METH_RK4: if DISTORT: #simple step size control rkstep = STEP # standard Runge-Kutta y = np.zeros((numChunk,6)) y[:,0:3] = point y[:,3:6] = velocity k1 = RK4f( y, h2) k2 = RK4f( y + 0.5*rkstep*k1, h2) k3 = RK4f( y + 0.5*rkstep*k2, h2) k4 = RK4f( y + rkstep*k3, h2) increment = rkstep/6. * (k1 + 2*k2 + 2*k3 + k4) velocity += increment[:,3:6] point += increment[:,0:3] #useful precalcs pointsqr = sqrnorm(point) #phi = np.arctan2(point[:,0],point[:,2]) #too heavy. Better an instance wherever it's needed. #normvel = normalize(velocity) #never used! BAD BAD BAD!! # FOG if FOGDO and (it%FOGSKIP == 0): phsphtaper = np.clip(0.8*(pointsqr - 1.0),0.,1.0) fogint = np.clip(FOGMULT * FOGSKIP * STEP / pointsqr,0.0,1.0) * phsphtaper fogcol = ones3 object_colour = blendcolors(fogcol,fogint,object_colour,object_alpha) object_alpha = blendalpha(fogint, object_alpha) # CHECK COLLISIONS # accretion disk if DISK_TEXTURE_INT != DT_NONE: mask_crossing = np.logical_xor( oldpoint[:,1] > 0., point[:,1] > 0.) #whether it just crossed the horizontal plane mask_distance = np.logical_and((pointsqr < DISKOUTERSQR), (pointsqr > DISKINNERSQR)) #whether it's close enough diskmask = np.logical_and(mask_crossing,mask_distance) if (diskmask.any()): #actual collision point by intersection lambdaa = - point[:,1]/velocity[:,1] colpoint = point + lambdaa[:,np.newaxis] * velocity colpointsqr = sqrnorm(colpoint) if DISK_TEXTURE_INT == DT_GRID: phi = np.arctan2(colpoint[:,0],point[:,2]) theta = np.arctan2(colpoint[:,1],norm(point[:,[0,2]])) diskcolor = np.outer( np.mod(phi,0.52359) < 0.261799, np.array([1.,1.,0.]) ) + \ np.outer(ones,np.array([0.,0.,1.]) ) diskalpha = diskmask elif DISK_TEXTURE_INT == DT_SOLID: diskcolor = np.array([1.,1.,.98]) diskalpha = diskmask elif DISK_TEXTURE_INT == DT_TEXTURE: phi = np.arctan2(colpoint[:,0],point[:,2]) uv = np.zeros((numChunk,2)) uv[:,0] = ((phi+2*np.pi)%(2*np.pi))/(2*np.pi) uv[:,1] = (np.sqrt(colpointsqr)-DISKINNER)/(DISKOUTER-DISKINNER) diskcolor = lookup ( texarr_disk, np.clip(uv,0.,1.)) #alphamask = (2.0*ransample) < sqrnorm(diskcolor) #diskmask = np.logical_and(diskmask, alphamask ) diskalpha = diskmask * np.clip(sqrnorm(diskcolor)/3.0,0.0,1.0) elif DISK_TEXTURE_INT == DT_BLACKBODY: temperature = np.exp(bb.disktemp(colpointsqr,9.2103)) if REDSHIFT: R = np.sqrt(colpointsqr) disc_velocity = 0.70710678 * \ np.power((np.sqrt(colpointsqr)-1.).clip(0.1),-.5)[:,np.newaxis] * \ np.cross(UPFIELD, normalize(colpoint)) gamma = np.power( 1 - sqrnorm(disc_velocity).clip(max=.99), -.5) # opz = 1 + z opz_doppler = gamma * ( 1. + np.einsum('ij,ij->i',disc_velocity,normalize(velocity))) opz_gravitational = np.power(1.- 1/R.clip(1),-.5) # (1+z)-redshifted Planck spectrum is still Planckian at temperature T temperature /= (opz_doppler*opz_gravitational).clip(0.1) intensity = bb.intensity(temperature) if DISK_INTENSITY_DO: diskcolor = np.einsum('ij,i->ij', bb.colour(temperature),DISK_MULTIPLIER*intensity)#np.maximum(1.*ones,DISK_MULTIPLIER*intensity)) else: diskcolor = bb.colour(temperature) iscotaper = np.clip((colpointsqr-DISKINNERSQR)*0.3,0.,1.) outertaper = np.clip(temperature/1000. ,0.,1.) diskalpha = diskmask * iscotaper * outertaper#np.clip(diskmask * DISK_ALPHA_MULTIPLIER *intensity,0.,1.) object_colour = blendcolors(diskcolor,diskalpha,object_colour,object_alpha) object_alpha = blendalpha(diskalpha, object_alpha) # event horizon oldpointsqr = sqrnorm(oldpoint) mask_horizon = np.logical_and((pointsqr < 1),(sqrnorm(oldpoint) > 1) ) if mask_horizon.any() : lambdaa = 1. - ((1.-oldpointsqr)/((pointsqr - oldpointsqr)))[:,np.newaxis] colpoint = lambdaa * point + (1-lambdaa)*oldpoint if HORIZON_GRID: phi = np.arctan2(colpoint[:,0],point[:,2]) theta = np.arctan2(colpoint[:,1],norm(point[:,[0,2]])) horizoncolour = np.outer( np.logical_xor(np.mod(phi,1.04719) < 0.52359,np.mod(theta,1.04719) < 0.52359), np.array([1.,0.,0.])) else: horizoncolour = BLACK#np.zeros((numPixels,3)) horizonalpha = mask_horizon object_colour = blendcolors(horizoncolour,horizonalpha,object_colour,object_alpha) object_alpha = blendalpha(horizonalpha, object_alpha) showprogress("generating sky layer...",i,q) vphi = np.arctan2(velocity[:,0],velocity[:,2]) vtheta = np.arctan2(velocity[:,1],norm(velocity[:,[0,2]]) ) vuv = np.zeros((numChunk,2)) vuv[:,0] = np.mod(vphi+4.5,2*np.pi)/(2*np.pi) vuv[:,1] = (vtheta+np.pi/2)/(np.pi) if SKY_TEXTURE_INT == DT_TEXTURE: col_sky = lookup(texarr_sky,vuv)[:,0:3] showprogress("generating debug layers...",i,q) ##debug color: direction of view vector #dbg_viewvec = np.clip(view + vec3(.5,.5,0.0),0.0,1.0) ##debug color: direction of final ray ##debug color: grid #dbg_grid = np.abs(normalize(velocity)) < 0.1 if SKY_TEXTURE_INT == ST_TEXTURE: col_bg = col_sky elif SKY_TEXTURE_INT == ST_NONE: col_bg = np.zeros((numChunk,3)) elif SKY_TEXTURE_INT == ST_FINAL: dbg_finvec = np.clip(normalize(velocity) + np.array([.5,.5,0.0])[np.newaxis,:],0.0,1.0) col_bg = dbg_finvec else: col_bg = np.zeros((numChunk,3)) showprogress("blending layers...",i,q) col_bg_and_obj = blendcolors(SKYDISK_RATIO*col_bg, ones ,object_colour,object_alpha) showprogress("beaming back to mothership.",i,q) # copy back in the buffer if not DISABLE_SHUFFLING: total_colour_buffer_preproc[chunk] = col_bg_and_obj else: total_colour_buffer_preproc[chunk[0]:(chunk[-1]+1)] = col_bg_and_obj #refresh display # NO: plt does not allow drawing outside main thread #if not DISABLE_DISPLAY: # showprogress("updating display...") # plt.imshow(total_colour_buffer_preproc.reshape((RESOLUTION[1],RESOLUTION[0],3))) # plt.draw() showprogress("garbage collection...",i,q) gc.collect() showprogress("Done.",i,q)
def raytrace_schedule(i,schedule,total_shared,q,drag): # this is the function running on each thread #global schedules,itcounters,chnkcounters,killers if len(schedule) == 0: return total_colour_buffer_preproc = tonumpyarray(total_shared) #schedule = schedules[i] itcounters[i] = 0 chnkcounters[i]= 0 for chunk in schedule: #if killers[i]: # break chnkcounters[i]+=1 #number of chunk pixels numChunk = chunk.shape[0] #useful constant arrays ones = np.ones((numChunk)) ones3 = np.ones((numChunk,3)) UPFIELD = np.outer(ones,np.array([0.,1.,0.])) BLACK = np.outer(ones,np.array([0.,0.,0.])) #arrays of integer pixel coordinates x = chunk % RESOLUTION[0] y = chunk / RESOLUTION[0] showprogress("Generating view vectors...",i,q) #the view vector in 3D space view = np.zeros((numChunk,3)) view[:,0] = x.astype(float)/RESOLUTION[0] - .5 view[:,1] = ((-y.astype(float)/RESOLUTION[1] + .5)*RESOLUTION[1])/RESOLUTION[0] #(inverting y coordinate) view[:,2] = 1.0 view[:,0]*=TANFOV view[:,1]*=TANFOV #rotating through the view matrix view = np.einsum('jk,ik->ij',viewMatrix,view) #original position point = np.outer(ones, CAMERA_POS) normview = normalize(view) velocity = np.copy(normview) # initializing the colour buffer object_colour = np.zeros((numChunk,3)) object_alpha = np.zeros(numChunk) # Drawing circle if DRAW_CIRCLE: t = -1 * np.einsum('ij,ij->i',point - OTHER_CENTER, velocity) / np.einsum('ij,ij->i',velocity,velocity) dist_vecs = point + np.einsum('ij,i->ij',velocity,t) - OTHER_CENTER squared_dists = np.einsum('ij,ij->i',dist_vecs,dist_vecs) theta = np.arctan2(dist_vecs[:,0], dist_vecs[:,1]) width = 0.05 dash_length = 1 mask_circle = np.logical_and.reduce([(t > 0),(squared_dists < OTHER_RADIUS * OTHER_RADIUS * (1 + width)), (squared_dists > OTHER_RADIUS * OTHER_RADIUS * (1 - width)), np.mod(theta, dash_length) < dash_length / 2]) circle_alpha = mask_circle * 0.3 object_colour = blendcolors(np.outer(ones,np.array([0.24,0.59,0.89])), circle_alpha, object_colour, object_alpha) object_alpha = blendalpha(circle_alpha, object_alpha) #squared angular momentum per unit mass (in the "Newtonian fantasy") #h2 = np.outer(sqrnorm(np.cross(point,velocity)),np.array([1.,1.,1.])) pointsqr = np.copy(ones3) for it in range(NITER): itcounters[i]+=1 if it%150 == 1: if killers[i]: break showprogress("Raytracing...",i,q) # STEPPING oldpoint = np.copy(point) #not needed for tracing. Useful for intersections if METHOD == METH_LEAPFROG: # TODO Not updated for multiple bodies #leapfrog method here feels good point += velocity * STEP if DISTORT: #this is the magical - 3/2 r^(-5) potential... accel = - 1.5 * h2 * point / np.power(sqrnorm(point),2.5)[:,np.newaxis] velocity += accel * STEP elif METHOD == METH_RK4: if DISTORT: #simple step size control rkstep = STEP # standard Runge-Kutta y = np.zeros((numChunk,6)) y[:,0:3] = point y[:,3:6] = velocity k1 = RK4f( y, drag=drag) k2 = RK4f( y + 0.5*rkstep*k1, drag=drag) k3 = RK4f( y + 0.5*rkstep*k2, drag=drag) k4 = RK4f( y + rkstep*k3, drag=drag) increment = rkstep/6. * (k1 + 2*k2 + 2*k3 + k4) velocity += increment[:,3:6] point += increment[:,0:3] for center, radius in zip(CENTERS,RADII): #useful precalcs pointsqr = sqrnorm(point - center) #phi = np.arctan2(point[:,0],point[:,2]) #too heavy. Better an instance wherever it's needed. #normvel = normalize(velocity) #never used! BAD BAD BAD!! # FOG if FOGDO and (it%FOGSKIP == 0): phsphtaper = np.clip(0.8*(pointsqr - 1.0),0.,1.0) fogint = np.clip(FOGMULT * FOGSKIP * STEP / pointsqr,0.0,1.0) * phsphtaper fogcol = ones3 object_colour = blendcolors(fogcol,fogint,object_colour,object_alpha) object_alpha = blendalpha(fogint, object_alpha) # CHECK COLLISIONS # accretion disk # TODO not implemented for multiple objects if DISK_TEXTURE_INT != DT_NONE: mask_crossing = np.logical_xor( oldpoint[:,1] > center[1], point[:,1] > center[1]) #whether it just crossed the horizontal plane mask_distance = np.logical_and((pointsqr < DISKOUTERSQR), (pointsqr > DISKINNERSQR)) #whether it's close enough diskmask = np.logical_and(mask_crossing,mask_distance) if (diskmask.any()): #actual collision point by intersection lambdaa = - (point[:,1] - center[1])/velocity[:,1] colpoint = point + lambdaa[:,np.newaxis] * velocity colpointsqr = sqrnorm(colpoint - center) if DISK_TEXTURE_INT == DT_GRID: phi = np.arctan2(colpoint[:,0] - center[0],point[:,2] - center[2]) theta = np.arctan2(colpoint[:,1] - center[1],norm(point[:,[0,2]] - center[[0,2]])) diskcolor = np.outer( np.mod(phi,0.52359) < 0.261799, np.array([1.,1.,0.]) ) + \ np.outer(ones,np.array([0.,0.,1.]) ) diskalpha = diskmask elif DISK_TEXTURE_INT == DT_SOLID: diskcolor = np.array([1.,1.,.98]) diskalpha = diskmask elif DISK_TEXTURE_INT == DT_TEXTURE: phi = np.arctan2(colpoint[:,0] - center[0],point[:,2] - center[2]) uv = np.zeros((numChunk,2)) uv[:,0] = ((phi+2*np.pi)%(2*np.pi))/(2*np.pi) uv[:,1] = (np.sqrt(colpointsqr)-DISKINNER)/(DISKOUTER-DISKINNER) diskcolor = lookup ( texarr_disk, np.clip(uv,0.,1.)) #alphamask = (2.0*ransample) < sqrnorm(diskcolor) #diskmask = np.logical_and(diskmask, alphamask ) diskalpha = diskmask * np.clip(sqrnorm(diskcolor)/3.0,0.0,1.0) elif DISK_TEXTURE_INT == DT_BLACKBODY: temperature = np.exp(bb.disktemp(colpointsqr,9.2103)) if REDSHIFT: R = np.sqrt(colpointsqr) disc_velocity = 0.70710678 * \ np.power((np.sqrt(colpointsqr)-1.).clip(0.1),-.5)[:,np.newaxis] * \ np.cross(UPFIELD, normalize(colpoint)) gamma = np.power( 1 - sqrnorm(disc_velocity).clip(max=.99), -.5) # opz = 1 + z opz_doppler = gamma * ( 1. + np.einsum('ij,ij->i',disc_velocity,normalize(velocity))) opz_gravitational = np.power(1.- 1/R.clip(1),-.5) # (1+z)-redshifted Planck spectrum is still Planckian at temperature T temperature /= (opz_doppler*opz_gravitational).clip(0.1) intensity = bb.intensity(temperature) if DISK_INTENSITY_DO: diskcolor = np.einsum('ij,i->ij', bb.colour(temperature),DISK_MULTIPLIER*intensity)#np.maximum(1.*ones,DISK_MULTIPLIER*intensity)) else: diskcolor = bb.colour(temperature) iscotaper = np.clip((colpointsqr-DISKINNERSQR)*0.3,0.,1.) outertaper = np.clip(temperature/1000. ,0.,1.) diskalpha = diskmask * iscotaper * outertaper#np.clip(diskmask * DISK_ALPHA_MULTIPLIER *intensity,0.,1.) object_colour = blendcolors(diskcolor,diskalpha,object_colour,object_alpha) object_alpha = blendalpha(diskalpha, object_alpha) # event horizon oldpointsqr = sqrnorm(oldpoint - center) mask_horizon = np.logical_and((pointsqr < radius**2),(oldpointsqr > radius**2) ) if mask_horizon.any() : if HORIZON_GRID: lambdaa = 1. - ((1. - oldpointsqr) / ((pointsqr - oldpointsqr)))[:, np.newaxis] colpoint = lambdaa * point + (1 - lambdaa) * oldpoint phi = np.arctan2(colpoint[:,0],point[:,2]) theta = np.arctan2(colpoint[:,1],norm(point[:,[0,2]])) horizoncolour = np.outer( np.logical_xor(np.mod(phi,1.04719) < 0.52359,np.mod(theta,1.04719) < 0.52359), np.array([1.,0.,0.])) else: horizoncolour = BLACK#np.zeros((numPixels,3)) horizonalpha = mask_horizon object_colour = blendcolors(horizoncolour,horizonalpha,object_colour,object_alpha) object_alpha = blendalpha(horizonalpha, object_alpha) # Rendering other object if OTHER_TEXTURE_INT != OT_NONE: oldpointsqr = sqrnorm(oldpoint - OTHER_CENTER) pointsqr = sqrnorm(point - OTHER_CENTER) mask_other = np.logical_and((pointsqr < OTHER_RADIUS ** 2), (oldpointsqr > OTHER_RADIUS ** 2)) if mask_other.any(): lambdaa = ((OTHER_RADIUS - np.sqrt(oldpointsqr)) / (np.sqrt(pointsqr) - np.sqrt(oldpointsqr)))[:, np.newaxis] colpoint = lambdaa * point + (1 - lambdaa) * oldpoint phi = np.arctan2(colpoint[:,0] - OTHER_CENTER[0],colpoint[:,2] - OTHER_CENTER[2]) theta = np.arctan2(colpoint[:,1] - OTHER_CENTER[1],norm(colpoint[:,[0,2]] - OTHER_CENTER[[0,2]])) vuv = np.zeros((numChunk, 2)) vuv[:, 0] = np.mod(phi + 4.5, 2 * np.pi) / (2 * np.pi) vuv[:, 1] = (-theta + np.pi / 2) / (np.pi) other_colour = lookup(texarr_other, vuv)[:,0:3] other_alpha = mask_other object_colour = blendcolors(other_colour,other_alpha,object_colour,object_alpha) object_alpha = blendalpha(other_alpha, object_alpha) if OTHER_TEXTURE_INT != OT_NONE: showprogress("computing final intersections...", i, q) # Creating intersection mask t = -1 * np.einsum('ij,ij->i',point - OTHER_CENTER, velocity) / multisqrnorm(velocity) dist_vecs = point + np.einsum('ij,i->ij',velocity,t) - OTHER_CENTER squared_dists = multisqrnorm(dist_vecs) mask_other_end = np.logical_and((t > 0),(squared_dists <= OTHER_RADIUS * OTHER_RADIUS)) # Calculating actual intersection points a = multidot(velocity, velocity) b = 2 * multidot(point - OTHER_CENTER,velocity) c = multisqrnorm(point - OTHER_CENTER) - OTHER_RADIUS ** 2 t = (-b - np.sqrt(b * b - 4 * a * c)) / (2 * a) mask_other_end = np.logical_and(mask_other_end, ~np.isnan(t)) t[np.isnan(t)] = 0 mask_other_end = np.logical_and(mask_other_end, t >= 0) intersections = point + np.einsum('ij,i->ij',velocity,t) - OTHER_CENTER phi = np.arctan2(intersections[:,0],intersections[:,2]) theta = np.arctan2(intersections[:,1],norm(intersections[:,[0,2]])) vuv = np.zeros((numChunk, 2)) vuv[:, 0] = np.mod(phi + 4.5, 2 * np.pi) / (2 * np.pi) vuv[:, 1] = (theta + np.pi / 2) / (np.pi) other_colour = lookup(texarr_other, vuv)[:, 0:3] other_alpha = mask_other_end object_colour = blendcolors(other_colour, other_alpha, object_colour, object_alpha) object_alpha = blendalpha(other_alpha, object_alpha) showprogress("generating sky layer...", i, q) vphi = np.arctan2(velocity[:,0],velocity[:,2]) vtheta = np.arctan2(velocity[:,1],norm(velocity[:,[0,2]]) ) vuv = np.zeros((numChunk,2)) vuv[:,0] = np.mod(vphi+4.5,2*np.pi)/(2*np.pi) vuv[:,1] = (vtheta+np.pi/2)/(np.pi) if SKY_TEXTURE_INT == DT_TEXTURE: col_sky = lookup(texarr_sky,vuv)[:,0:3] showprogress("generating debug layers...",i,q) ##debug color: direction of view vector #dbg_viewvec = np.clip(view + vec3(.5,.5,0.0),0.0,1.0) ##debug color: direction of final ray ##debug color: grid #dbg_grid = np.abs(normalize(velocity)) < 0.1 if SKY_TEXTURE_INT == ST_TEXTURE: col_bg = col_sky elif SKY_TEXTURE_INT == ST_NONE: col_bg = np.zeros((numChunk,3)) elif SKY_TEXTURE_INT == ST_FINAL: dbg_finvec = np.clip(normalize(velocity) + np.array([.5,.5,0.0])[np.newaxis,:],0.0,1.0) col_bg = dbg_finvec else: col_bg = np.zeros((numChunk,3)) showprogress("blending layers...",i,q) col_bg_and_obj = blendcolors(SKYDISK_RATIO*col_bg, ones ,object_colour,object_alpha) showprogress("beaming back to mothership.",i,q) # copy back in the buffer if not DISABLE_SHUFFLING: total_colour_buffer_preproc[chunk] = col_bg_and_obj else: total_colour_buffer_preproc[chunk[0]:(chunk[-1]+1)] = col_bg_and_obj #refresh display # NO: plt does not allow drawing outside main thread #if not DISABLE_DISPLAY: # showprogress("updating display...") # plt.imshow(total_colour_buffer_preproc.reshape((RESOLUTION[1],RESOLUTION[0],3))) # plt.draw() showprogress("garbage collection...",i,q) gc.collect() showprogress("Done.",i,q)
def raytrace_schedule(i, schedule, total_shared, q): # this is the function running on each thread #global schedules,itcounters,chnkcounters,killers if len(schedule) == 0: return total_colour_buffer_preproc = tonumpyarray(total_shared) #schedule = schedules[i] itcounters[i] = 0 chnkcounters[i] = 0 for chunk in schedule: #if killers[i]: # break chnkcounters[i] += 1 #number of chunk pixels numChunk = chunk.shape[0] #useful constant arrays ones = np.ones((numChunk)) ones3 = np.ones((numChunk, 3)) UPFIELD = np.outer(ones, np.array([0., 1., 0.])) BLACK = np.outer(ones, np.array([0., 0., 0.])) #arrays of integer pixel coordinates x = chunk % RESOLUTION[0] y = chunk / RESOLUTION[0] showprogress("Generating view vectors...", i, q) #the view vector in 3D space view = np.zeros((numChunk, 3)) view[:, 0] = x.astype(float) / RESOLUTION[0] - .5 view[:, 1] = ((-y.astype(float) / RESOLUTION[1] + .5) * RESOLUTION[1]) / RESOLUTION[0] #(inverting y coordinate) view[:, 2] = 1.0 view[:, 0] *= TANFOV view[:, 1] *= TANFOV #rotating through the view matrix view = np.einsum('jk,ik->ij', viewMatrix, view) #original position point = np.outer(ones, CAMERA_POS) normview = normalize(view) velocity = np.copy(normview) # initializing the colour buffer object_colour = np.zeros((numChunk, 3)) object_alpha = np.zeros(numChunk) #squared angular momentum per unit mass (in the "Newtonian fantasy") #h2 = np.outer(sqrnorm(np.cross(point,velocity)),np.array([1.,1.,1.])) h2 = sqrnorm(np.cross(point, velocity))[:, np.newaxis] pointsqr = np.copy(ones3) for it in range(NITER): itcounters[i] += 1 if it % 150 == 1: if killers[i]: break showprogress("Raytracing...", i, q) # STEPPING oldpoint = np.copy( point) #not needed for tracing. Useful for intersections if METHOD == METH_LEAPFROG: #leapfrog method here feels good point += velocity * STEP if DISTORT: #this is the magical - 3/2 r^(-5) potential... accel = -1.5 * h2 * point / sixth(point)[:, np.newaxis] velocity += accel * STEP elif METHOD == METH_RK4: #simple step size control rkstep = STEP # standard Runge-Kutta y = np.zeros((numChunk, 6)) y[:, 0:3] = point y[:, 3:6] = velocity k1 = RK4f(y, h2) k2 = RK4f(y + 0.5 * rkstep * k1, h2) k3 = RK4f(y + 0.5 * rkstep * k2, h2) k4 = RK4f(y + rkstep * k3, h2) increment = rkstep / 6. * (k1 + 2 * k2 + 2 * k3 + k4) point += increment[:, 0:3] velocity += increment[:, 3:6] #useful precalcs pointsqr = sqrnorm(point) #phi = np.arctan2(point[:,0],point[:,2]) #too heavy. Better an instance wherever it's needed. #normvel = normalize(velocity) #never used! BAD BAD BAD!! # FOG if FOGDO and (it % FOGSKIP == 0): fogint = np.clip(FOGMULT * FOGSKIP * STEP / sqrnorm(point), 0.0, 1.0) fogcol = ones3 object_colour = blendcolors(fogcol, fogint, object_colour, object_alpha) object_alpha = blendalpha(fogint, object_alpha) # CHECK COLLISIONS # accretion disk if DISK_TEXTURE != "none": mask_crossing = np.logical_xor( oldpoint[:, 1] > 0., point[:, 1] > 0.) #whether it just crossed the horizontal plane mask_distance = np.logical_and( (pointsqr < DISKOUTERSQR), (pointsqr > DISKINNERSQR)) #whether it's close enough diskmask = np.logical_and(mask_crossing, mask_distance) if (diskmask.any()): #actual collision point by intersection lambdaa = -point[:, 1] / velocity[:, 1] colpoint = point + lambdaa[:, np.newaxis] * velocity colpointsqr = sqrnorm(colpoint) if DISK_TEXTURE == "grid": phi = np.arctan2(colpoint[:, 0], point[:, 2]) theta = np.arctan2(colpoint[:, 1], norm(point[:, [0, 2]])) diskcolor = np.outer( np.mod(phi,0.52359) < 0.261799, np.array([1.,1.,0.]) ) + \ np.outer(ones,np.array([0.,0.,1.]) ) diskalpha = diskmask elif DISK_TEXTURE == "solid": diskcolor = np.array([1., 1., .98]) diskalpha = diskmask elif DISK_TEXTURE == "texture": phi = np.arctan2(colpoint[:, 0], point[:, 2]) uv = np.zeros((numChunk, 2)) uv[:, 0] = ((phi + 2 * np.pi) % (2 * np.pi)) / (2 * np.pi) uv[:, 1] = (np.sqrt(colpointsqr) - DISKINNER) / (DISKOUTER - DISKINNER) diskcolor = lookup(texarr_disk, np.clip(uv, 0., 1.)) #alphamask = (2.0*ransample) < sqrnorm(diskcolor) #diskmask = np.logical_and(diskmask, alphamask ) diskalpha = diskmask * np.clip( sqrnorm(diskcolor) / 3.0, 0.0, 1.0) elif DISK_TEXTURE == "blackbody": temperature = np.exp(bb.disktemp(colpointsqr, 9.2103)) if REDSHIFT: R = np.sqrt(colpointsqr) disc_velocity = 0.70710678 * \ np.power((np.sqrt(colpointsqr)-1.).clip(0.1),-.5)[:,np.newaxis] * \ np.cross(UPFIELD, normalize(colpoint)) gamma = np.power( 1 - sqrnorm(disc_velocity).clip(max=.99), -.5) # opz = 1 + z opz_doppler = gamma * ( 1. + np.einsum('ij,ij->i', disc_velocity, normalize(velocity))) opz_gravitational = np.power( 1. - 1 / R.clip(1), -.5) # (1+z)-redshifted Planck spectrum is still Planckian at temperature T temperature /= (opz_doppler * opz_gravitational).clip(0.1) intensity = bb.intensity(temperature) diskcolor = np.einsum( 'ij,i->ij', bb.colour(temperature), np.maximum(1. * ones, DISK_MULTIPLIER * intensity)) iscotaper = np.clip((colpointsqr - DISKINNERSQR) * 0.5, 0., 1.) diskalpha = iscotaper * np.clip( diskmask * DISK_ALPHA_MULTIPLIER * intensity, 0., 1.) object_colour = blendcolors(diskcolor, diskalpha, object_colour, object_alpha) object_alpha = blendalpha(diskalpha, object_alpha) # event horizon oldpointsqr = sqrnorm(oldpoint) mask_horizon = np.logical_and((pointsqr < 1), (sqrnorm(oldpoint) > 1)) if mask_horizon.any(): lambdaa = ((1. - oldpointsqr) / ((pointsqr - oldpointsqr)))[:, np.newaxis] colpoint = lambdaa * point + (1 - lambdaa) * oldpoint if HORIZON_GRID: phi = np.arctan2(colpoint[:, 0], point[:, 2]) theta = np.arctan2(colpoint[:, 1], norm(point[:, [0, 2]])) horizoncolour = np.outer( np.logical_xor( np.mod(phi, 1.04719) < 0.52359, np.mod(theta, 1.04719) < 0.52359), np.array([1., 0., 0.])) else: horizoncolour = BLACK #np.zeros((numPixels,3)) horizonalpha = mask_horizon object_colour = blendcolors(horizoncolour, horizonalpha, object_colour, object_alpha) object_alpha = blendalpha(horizonalpha, object_alpha) showprogress("generating sky layer...", i, q) vphi = np.arctan2(velocity[:, 0], velocity[:, 2]) vtheta = np.arctan2(velocity[:, 1], norm(velocity[:, [0, 2]])) vuv = np.zeros((numChunk, 2)) vuv[:, 0] = np.mod(vphi + 4.5, 2 * np.pi) / (2 * np.pi) vuv[:, 1] = (vtheta + np.pi / 2) / (np.pi) if SKY_TEXTURE == 'texture': col_sky = lookup(texarr_sky, vuv)[:, 0:3] showprogress("generating debug layers...", i, q) ##debug color: direction of view vector #dbg_viewvec = np.clip(view + vec3(.5,.5,0.0),0.0,1.0) ##debug color: direction of final ray ##debug color: grid #dbg_grid = np.abs(normalize(velocity)) < 0.1 if SKY_TEXTURE == 'texture': col_bg = col_sky elif SKY_TEXTURE == 'none': col_bg = np.zeros((numChunk, 3)) elif SKY_TEXTURE == 'final': dbg_finvec = np.clip( normalize(velocity) + vec3(.5, .5, 0.0), 0.0, 1.0) col_bg = dbg_finvec else: col_bg = np.zeros((numChunk, 3)) showprogress("blending layers...", i, q) col_bg_and_obj = blendcolors(SKYDISK_RATIO * col_bg, ones, object_colour, object_alpha) showprogress("beaming back to mothership.", i, q) # copy back in the buffer if not DISABLE_SHUFFLING: total_colour_buffer_preproc[chunk] = col_bg_and_obj else: total_colour_buffer_preproc[chunk[0]:(chunk[-1] + 1)] = col_bg_and_obj #refresh display # NO: plt does not allow drawing outside main thread #if not DISABLE_DISPLAY: # showprogress("updating display...") # plt.imshow(total_colour_buffer_preproc.reshape((RESOLUTION[1],RESOLUTION[0],3))) # plt.draw() showprogress("garbage collection...", i, q) gc.collect() showprogress("Done.", i, q)