import java.awt.*; public class LittleGhost1 extends PixApplet { final int LEFT_EYE = 0, RIGHT_EYE = 1, BODY = 2, SKIRT = 3, LEFT_ARM = 4, RIGHT_ARM = 5, TOP_WAFER = 6, YUMMY_CREME = 7, BOTTOM_WAFER = 8, NSHAPES = 9; Shape shapes[] = new Shape[NSHAPES]; double c0[] = {1, 1,1, 0,0,0, 0,0,0, -1}; // ELLIPSOID double c1[] = {1,-1,1, 0,0,0, 0,0,0, -1}; // HYPERBOLOID KM km = new KM(); double nearest_t = 0, theta = 0; double focal_length = 3.; // FOCAL LENGTH OF "LENS" double v[] = {0,0,1}; // VIEW POINT double w[] = {0,0,-focal_length}; // RAY DIRECTION double t[] = new double[2]; // PLACE TO STORE ROOTS double p[] = {0,0,0}; // SURFACE POINT double n[] = {0,0,0}; // SURFACE NORMAL double L[][] = {{1,1,1},{-1,.8,.1}}; // LIGHT DIRECTIONS boolean isAnimating = false; boolean isBlinking = false; boolean isCremeFilling = false; boolean isEdgeSmoothing = false; boolean isRising = false; boolean isShowingRays = false; boolean isTexturing = false; boolean isWideBase = false; public void init() { super.init(); Material cookieMaterial = new Material(), whiteMaterial = new Material(), blackMaterial = new Material(); cookieMaterial.setAmbient(.05,.02,0).setDiffuse(.35,.14,0).setSpecular(.25,.25,.25,10); cookieMaterial.addNoise(4,.27); whiteMaterial.setAmbient(.55,.4,.3).setDiffuse(.7,.7,.7).setSpecular(0,0,0,1); blackMaterial.setAmbient(0,0,0).setDiffuse(0,0,0).setSpecular(1,1,1,10); for (int i = 0 ; i < shapes.length ; i++) { shapes[i] = new Quadratic(i==SKIRT ? c1 : c0); Quadratic q = (Quadratic)shapes[i]; switch (i) { case SKIRT: q.addClipPlane(0, 1,0,.3); q.addClipPlane(0,-1,0,.3); break; case BODY: q.addClipPlane(0,1,0,.7); break; } switch (i) { case TOP_WAFER: case BOTTOM_WAFER: shapes[i].material = cookieMaterial; break; case BODY: case SKIRT: case LEFT_ARM: case RIGHT_ARM: case YUMMY_CREME: shapes[i].material = whiteMaterial; break; case LEFT_EYE: case RIGHT_EYE: shapes[i].material = blackMaterial; break; } } for (int i = 0 ; i < L.length ; i++) normalize(L[i]); mark = new int[W*H]; } int mark[]; public void setPix(int frame) { super.setPix(frame); animate(frame); for (int i = 0 ; i < W*H ; i++) mark[i] = 0; // RENDER EVERY 4TH x 4TH PIXEL for (int row = 0 ; row < H ; row += 4) for (int col = 0 ; col < W ; col += 4) render(row, col); // RENDER ALL PIXELS ALONG RIGHT AND BOTTOM EDGES for (int row = 0 ; row < H ; row++) render(row,W-1); for (int col = 0 ; col < W ; col++) render(H-1,col); // SELECTIVELY SAMPLE DOWN TO 2x2 for (int row = 0 ; row < H ; row += 4) for (int col = 0 ; col < W-4 ; col += 4) interpolate(xy2i(col,row),xy2i(col+4,row), row,col+2, 2); for (int row = 0 ; row < H-4 ; row += 4) for (int col = 0 ; col < W ; col += 2) interpolate(xy2i(col,row),xy2i(col,row+4), row+2,col, 2); // SELECTIVELY SAMPLE DOWN TO 1x1 for (int row = 0 ; row < H ; row += 2) for (int col = 0 ; col < W-2 ; col += 2) interpolate(xy2i(col,row),xy2i(col+2,row), row,col+1, 1); for (int row = 0 ; row < H-2 ; row += 2) for (int col = 0 ; col < W ; col++) interpolate(xy2i(col,row),xy2i(col,row+2), row+1,col, 1); // SUPERSAMPLE PIXELS WHEREVER COLOR CHANGES RAPIDLY if (isEdgeSmoothing) { for (int row = 0 ; row < H-1 ; row++) for (int col = 0 ; col < W-1 ; col++) { int i = xy2i(col, row); unpack(a, pix[i]); unpack(b, pix[i+1]); unpack(c, pix[i+W]); unpack(d, pix[i+W+1]); if (edge(a,b,0) || edge(b,d,0) || edge(d,c,0) || edge(c,a,0)) { render(row+.5,col ,b); render(row ,col+.5,c); render(row+.5,col+.5,d); pix[i] = pack(a[0]+b[0]+c[0]+d[0]>>2, a[1]+b[1]+c[1]+d[1]>>2, a[2]+b[2]+c[2]+d[2]>>2); } } } if (isShowingRays) { for (int i = 0 ; i < W*H ; i++) switch (mark[i]) { case 0: break; case 1: pix[i] = pack(180,0,0); break; case 2: pix[i] = pack(0,180,0); break; case 3: pix[i] = pack(255,255,255); break; } } damage = true; } int a[] = new int[3], b[] = new int[3], c[] = new int[3], d[] = new int[3]; void interpolate(int i1, int i2, int row, int col, int eps) { unpack(a, pix[i1]); unpack(b, pix[i2]); if (edge(a, b, eps)) render(row, col); else pix[xy2i(col,row)] = pack(a[0]+b[0]>>1,a[1]+b[1]>>1,a[2]+b[2]>>1); } boolean edge(int a[], int b[], int shft) { int dr = b[0]-a[0], dg = b[1]-a[1], db = b[2]-a[2]; return (dr*dr + 2*dg*dg + 3*db*db > (1000 >> shft)); } double ndw[] = {0,0,0}; int rgb[] = {0,0,0}; void render(double row, double col) { render(row, col, rgb); int i = xy2i((int)col, (int)row); pix[i] = pack(rgb[0],rgb[1],rgb[2]); } void render(double row, double col, int rgb[]) { mark[xy2i((int)col, (int)row)]++; w[0] = col / (double)W - 0.5; // CONSTRUCT RAY DIRECTION VECTOR w[1] = -row / (double)W + 0.5 * H / W; w[2] = -focal_length; normalize(w); Shape nearest_s = traceRay(v, w, t); if (nearest_s != null) { // IF RAY HITS ANYTHING, SHADE NEAREST SHAPE p[0] = v[0] + nearest_t * w[0]; // COMPUTE SURFACE POINT p[1] = v[1] + nearest_t * w[1]; p[2] = v[2] + nearest_t * w[2]; Material m = nearest_s.material; double red = m.ambient[0], grn = m.ambient[1], blu = m.ambient[2]; double nde = -100; for (int i = 0 ; i < L.length ; i++) if (traceRay(p, L[i], t) == null) { // IF NOT IN SHADOW, nearest_s.computeNormal(p, n); // GET SURFACE NORMAL double d = dot(L[i],n); if (d > 0) { // ADD DIFFUSE d = d * d; red += d * m.diffuse[0]; grn += d * m.diffuse[1]; blu += d * m.diffuse[2]; if (nde < -1) { nde = -dot(n,w); ndw[0] = 2 * n[0]*nde + w[0]; ndw[1] = 2 * n[1]*nde + w[1]; ndw[2] = 2 * n[2]*nde + w[2]; } double s = dot(ndw, L[i]); if (s > 0) { // ADD SPECULAR s = Math.pow(s, m.specular[3]); red += s * m.specular[0]; grn += s * m.specular[1]; blu += s * m.specular[2]; } } } if (isTexturing) { // COMPUTE NOISE TEXTURE if (nearest_s.material.nBands > 0) { Matrix.transform(nearest_s.matrix, p, pp); double t = 1; for (int b = 0 ; b < nearest_s.material.nBands ; b++) { double freq = nearest_s.material.noise[2*b ]; double ampl = nearest_s.material.noise[2*b+1]; t *= 1 + ampl * INoise.noise((int)(freq * pp[0]), (int)(freq * pp[1]), (int)(freq * pp[2])); } red *= t; grn *= t; blu *= t; } } rgb[0] = f2i(red); rgb[1] = f2i(grn); rgb[2] = f2i(blu); } else { // MISSED EVERYTHING: RENDER BACKGROUND COLOR rgb[0] = 40; rgb[1] = 40; rgb[2] = 120; } } int f2i(double f) { return (int)Math.max(0,Math.min(255,255*f)); } double pp[] = {0,0,0}; double rate = 20., old_time = 0, elapsed = 0, throttle = 0; void animate(int frame) { double time = System.currentTimeMillis() / 1000.; if (old_time == 0) old_time = time; throttle = .9 * throttle + .1 * (time - old_time); //System.out.println(throttle); elapsed += rate * (time - old_time); old_time = time; rate = .9*rate + .1*20; boolean blink = false; if (isBlinking) { blink = (elapsed % 8 > 6) && (elapsed % 9.1 > 7); } double cos = 1, sin = 0; if (isAnimating) { cos = Math.cos(.5*elapsed); sin = Math.sin(.5*elapsed); } double t = .1, r = 1; if (isRising) { t = .1 * (1 + .0025*rate * cos); r = (rate-15)/25; } else rate = 20; km.initFrame(); km.rotateX(.2); km.rotateY(theta); km.translate(0,-.01,0); // COOKIE km.push(); km.translate(0,-.03,0); km.rotateX(.1); km.scale(.1,.03,.1); shapes[TOP_WAFER].transform(km.get()); km.translate(0,-1,0); km.push(); double tt = lerp(r, 1.1-2*t, .9); km.scale(tt,isCremeFilling ? 1.3 : 0,tt); shapes[YUMMY_CREME].transform(km.get()); km.pop(); km.translate(0,isCremeFilling ? -1 : -1000,0); shapes[BOTTOM_WAFER].transform(km.get()); km.pop(); if (isAnimating) { km.rotateZ(r*.2*Math.sin(.22*elapsed)); km.rotateX(r*.2*Math.sin(.19*elapsed)); } km.translate(0,-.02+.03*(t/.1*rate-20)/20,0); // MAIN BODY OF GHOST km.push(); km.scale(.003/t,.5*t,.003/t); km.translate(0,1,0); shapes[BODY].transform(km.get()); km.push(); km.translate(.3,.65,.5); km.scale(.3,blink ? 0 : .22,.3); shapes[LEFT_EYE].transform(km.get()); km.pop(); km.push(); km.translate(-.3,.65,.5); km.scale(.3,blink ? 0 : .22,.3); shapes[RIGHT_EYE].transform(km.get()); km.pop(); km.translate(0,-.5,0); km.push(); km.translate(0,-.12,0); km.scale(.84,isWideBase ? .45 : 0,.84); shapes[SKIRT].transform(km.get()); km.pop(); km.pop(); km.translate(0,t - .04,0); // ARMS km.push(); km.translate(.01,0,0); km.rotateZ(r-.5+r*.5*sin); km.rotateX(r*.3*sin); km.rotateY(r*.5*cos); km.scale(.03,.01,.015); km.translate(.7,0,0); shapes[LEFT_ARM].transform(km.get()); km.pop(); km.push(); km.translate(-.01,0,0); km.rotateZ(-(r-.5)-r*.5*sin); km.rotateX(-r*.3*sin); km.rotateY(-r*.5*cos); km.scale(.03,.01,.015); km.translate(-.7,0,0); shapes[RIGHT_ARM].transform(km.get()); km.pop(); } double V[] = new double[24]; Shape traceRay(double v[], double w[], double t[]) { Shape nearest_s = null; nearest_t = 100000; Quadratic.compute_V(v, w, V); for (int i = 0 ; i < shapes.length ; i++) { Shape s = shapes[i]; if (s.traceRay(V,t)>0 && t[0]>0 && t[0]