/** (c) subpixel 2009 - http://subpixels.com * * class Scene * 2009-01-20 Created by subpixel */ static final int numFliers = 96; // The number of flying objects static final int numChannels = 10; // Number of independent control channels static final int numFlierChannels = 8; // Number of channels to use for fliers static final int maxEffects = 10; // Number of effects available static final float defaultSpin = 0.1; static final float spinRateStep = 0.01; static final float tiltStep = PI / 10; // ---------------------------------------------------------------------------- // Global values for the scene as a whole // ---------------------------------------------------------------------------- public class Scene { PApplet pa; // Communication with Processing (maybe not required) float time; //TODO: create beat-synchronised clock // Rotational orientation float targetThetaX; float targetThetaY; float targetThetaZ; float thetaX; float thetaY; float thetaZ; // Angular veloocity float omegaX; float omegaY; float omegaZ; // The effects available for use in the scene EffectInfo[] effectsInfo; int numEffects; // The number of efefcts registered Channel[] channels; // Independent control channels Channel master; // The master control channel (mapped to channels[0] for now) Flier[] fliers; // The flying objects PImage[] images; // Array of background images to choose from int numImages; // Handy count of the number of images loaded PImage bg; // To hold a background image if you want one int bgImageIndex; // Index of background image in the images[] array int bgx; // Size information for scaling background image int bgy; // Size information for scaling background image // User controlled parameters boolean ctrlPaused = false; // Want to be able to toggle paused/unpaused boolean ctrlAuto = true; // Auto mode? boolean ctrlBackground = true; // Show background? boolean ctrlRegular = false; // Regular size/spacing or random? boolean ctrlSpin = true; // Use base spin rate? boolean ctrlSpinNoise = true; // Apply noise to the spin? boolean ctrlTumble = true; // Objects tumble as they fly? // Time warping: eg 1 = not warped. 0 = stopped. -1 = reversed. 2 = double speed. float timeWarp = 1.0; int autoCountdown = 0; // Frames to go until next random action (if auto==true) float targetSpinRate; // Desired spinRate (ease to this value) float spinRate; // Base angular velocity for spin boolean spinAdjust; // Adjust the scene spin? float spinAdjustment; // Factor to adjust the spin (rate?) by float spinNoiseScale = 0.1; // How much the spin is affected by noise float spinNoiseTimescale = 0.001; // How quickly the spin is affected float spinDamping = 0.92; // Drag/damping factor for scene rotation // -------------------------------------------------------------------------- // Constructor // -------------------------------------------------------------------------- Scene(PApplet papplet, String backgroundImageFileName) { this(papplet, new String[] { backgroundImageFileName }); } Scene(PApplet papplet, String[] bgImageFileNames) { // ------------------------------------------------------------------------ // Communication with the Processing Applet // ------------------------------------------------------------------------ this.pa = papplet; // ------------------------------------------------------------------------ // Prepare the background images and work out some sizing information // ------------------------------------------------------------------------ numImages = bgImageFileNames.length; images = new PImage[bgImageFileNames.length]; for (int i = 0; i < bgImageFileNames.length; i++) { // Load the image from a file images[i] = loadImage(bgImageFileNames[i]); } setBgImage(0); // ------------------------------------------------------------------------ // Register the effects // ------------------------------------------------------------------------ effectsInfo = new EffectInfo[] { new EffectInfo("Yellow Sphere", "Draws a sphere around the central box"), new EffectInfo("Yellow Boxes", "Colours the boxes yellow instead of white"), new EffectInfo("Heli Blades", "HeliCcoptor blades above box"), new EffectInfo("Heli Cockpit", "HeliCcoptor cockpit in front of box") }; numEffects = effectsInfo.length; // ------------------------------------------------------------------------ // Set up control channels // ------------------------------------------------------------------------ channels = new Channel[numChannels + 1]; // The control channels for (int i = 0; i <= numChannels; i++) { String id = i == 0 ? "MASTER CHANNEL" : "Channel_" + i; channels[i] = new Channel(this, id); } master = channels[0]; // Ha! :o) // ------------------------------------------------------------------------ // Initialise all of the flying objects // ------------------------------------------------------------------------ fliers = new Flier[numFliers]; for(int i = 0; i < numFliers; i++) { Channel channel = channels[i % numFlierChannels + 1]; fliers[i] = new Flier(this, channel); } } // -------------------------------------------------------------------------- // Main update method // -------------------------------------------------------------------------- void update() { // ------------------------------------------------------------------------ // Timing considerations // ------------------------------------------------------------------------ time = millis(); //TODO: create beat-sychronised clock // ------------------------------------------------------------------------ // Scene orientation // ------------------------------------------------------------------------ if (ctrlSpin) { spinRate = lerp(spinRate, targetSpinRate, 0.05); omegaZ = spinRate; if (ctrlSpinNoise) // Spin the entire scene { // Take a noise sample based on time elapsed. Manipulate the [0,1) value // to a [-0.5, 0.5) value, scale it and add to scene spin omegaZ += (noise(time * spinNoiseTimescale) - 0.5) * spinNoiseScale; } if (spinAdjust) omegaZ *= spinAdjustment; } else { omegaZ *= spinDamping; } // Apply a drag/damping factor to the X and Y spin so it comes to rest omegaX *= spinDamping; omegaY *= spinDamping; targetThetaX += omegaX; targetThetaY += omegaY; targetThetaZ += omegaZ; thetaX = lerp(thetaX, targetThetaX, 0.1); //TODO: magic number thetaY = lerp(thetaY, targetThetaY, 0.1); //TODO: magic number thetaZ = lerp(thetaZ, targetThetaZ, 0.1); //TODO: magic number // ------------------------------------------------------------------------ // Other scene automation // ------------------------------------------------------------------------ if (ctrlAuto) // Automatic background toggle { //TODO: this would be better using a synchronised oscilator autoCountdown--; if (autoCountdown <= 0) { if (!ctrlBackground && random(1) < 0.25) { toggleBackground(); } else { bgImageIndex = int(random(numImages)); setBgImage(bgImageIndex); } newAutoCountdown(); } } // ------------------------------------------------------------------------ // Effect channel updates // ------------------------------------------------------------------------ for (int i = 0; i < numChannels; i++) { channels[i].update(); } // ------------------------------------------------------------------------ // Update flying objects // ------------------------------------------------------------------------ for (int i = 0; i < numFliers; i++) { fliers[i].update(); } } // -------------------------------------------------------------------------- // Main draw method // -------------------------------------------------------------------------- void draw() { colorMode(HSB, 100); background(0); sphereDetail(7); // Camera at (0, 0, 2000) looking at the origin (0, 0, 0), Y-axis is "up" // This puts (0, 0, z) at the centre of the window camera(0, 0, 2000, 0, 0, 0, 0, 1, 0); rotateX(thetaX); // Orient the scene rotateY(thetaY); // Orient the scene rotateZ(thetaZ); // Orient the scene if (ctrlBackground) // Show background image? { image(bg, -bgx, -bgy, bgx << 1, bgy << 1); // /* ***** ALTERNATE CODE ***** */ // textureMode(NORMALIZED); // tint(100, 50); // 50% opactiy // beginShape(); // texture(bg); // vertex(-bgx, -bgy, 0, 0); // vertex(bgx, -bgy, 1, 0); // vertex(bgx, bgy, 1, 1); // vertex(-bgx, bgy, 0, 1); // endShape(); } // Set up lighting for the scene // A light pointing straight up and another pointing straight down directionalLight(51, 102, 126, 0, 1, 0); directionalLight(10, 20, 30, 0, -1, 0); for (int i = 0; i < numFliers; i++) { fliers[i].draw(); } } // -------------------------------------------------------------------------- // Handle keyboard input // -------------------------------------------------------------------------- void keyPressed() { final int PAGE_UP = 33; final int PAGE_DOWN = 34; final int END = 35; final int HOME = 36; println("keyPressed(): keyCode: " + keyCode + ", key: [" + key + "] (" + (int)key + ")"); if (key == CODED) switch (keyCode) { case UP: targetThetaX += tiltStep; break; case DOWN: targetThetaX -= tiltStep; break; case LEFT: targetThetaY -= tiltStep; break; case RIGHT: targetThetaY += tiltStep; break; case END: case HOME: // Return to initial orientation; targetThetaX = 0; targetThetaY = 0; break; case PAGE_DOWN: spinRateDecrease(); spinOn(); break; case PAGE_UP: spinRateIncrease(); spinOn(); break; } else switch(key) { case ' ': togglePaused(); break; case 'A': case 'a': toggleAuto(); break; case 'B': case 'b': toggleBackground(); break; case 'E': // RANDOM EFFECT TOGGLE on RANDOM CHANNEL randomFlierChannel().randomEffectToggle(); break; case 'e': // RANDOM EFFECT SHOT on RANDOM CHANNEL randomFlierChannel().randomEffectShot(); break; case 'N': case 'n': bgImageIndex++; if (bgImageIndex >= numImages) bgImageIndex = 0; setBgImage(bgImageIndex); break; case 'R': case 'r': master.toggleForceRegular(); break; case 'S': targetSpinRate = 0; break; case 's': toggleSpin(); break; case 'W': case 'w': toggleSpinNoise(); // "wiggle" :o) break; case 'T': case 't': master.toggleTumble(); break; case ',': master.velocityHalve(); break; case '.': master.velocityDouble(); break; case '/': master.toggleVelocityAdjust(); break; case '<': master.velocityDecrease(); master.velocityAdjustOn(); break; case '>': master.velocityIncrease(); break; case '?': master.velocityAdjustmentReset(); break; case '`': channels[0].randomAction(); break; // MASTER CHANNEL case '1': channels[1].randomAction(); break; case '2': channels[2].randomAction(); break; case '3': channels[3].randomAction(); break; case '4': channels[4].randomAction(); break; case '5': channels[5].randomAction(); break; case '6': channels[6].randomAction(); break; case '7': channels[7].randomAction(); break; case '8': channels[8].randomAction(); break; case '9': channels[9].randomAction(); break; case '0': channels[10].randomAction(); break; case '~': channels[0].display = false; break; // MASTER CHANNEL case '!': channels[1].display = false; break; case '@': channels[2].display = false; break; case '#': channels[3].display = false; break; case '$': channels[4].display = false; break; case '%': channels[5].display = false; break; case '^': channels[6].display = false; break; case '&': channels[7].display = false; break; case '*': channels[8].display = false; break; case '(': channels[9].display = false; break; case ')': channels[10].display = false; break; } } // -------------------------------------------------------------------------- // Toggle methods // -------------------------------------------------------------------------- void togglePaused() { ctrlPaused = !ctrlPaused; if (ctrlPaused) noLoop(); else loop(); } void toggleAuto() { ctrlAuto = !ctrlAuto; if (ctrlAuto) newAutoCountdown(); } void toggleBackground() { ctrlBackground = !ctrlBackground; } void toggleSpin() { ctrlSpin = !ctrlSpin; } void spinOn() { ctrlSpin = true; } void spinOff() { ctrlSpin = false; } void toggleSpinNoise() { ctrlSpinNoise = !ctrlSpinNoise; } // -------------------------------------------------------------------------- // Helper methods // -------------------------------------------------------------------------- void newAutoCountdown() { //TODO: this would be better using a synchronised oscilator // Use a shorter countdown if the background isn't being shown now autoCountdown = ctrlBackground ? int(random(100)) + 50 : 10; } void spinRateIncrease() { if (ctrlSpin) targetSpinRate += spinRateStep; ctrlSpin = true; } void spinRateDecrease() { if (ctrlSpin) targetSpinRate -= spinRateStep; ctrlSpin = true; } Channel randomFlierChannel() { int channelNo = int(random(numFlierChannels)) + 1; return channels[channelNo]; } void setBgImage(int imageIndex) { imageIndex = constrain(imageIndex, 0, numImages - 1); bg = images[imageIndex]; // Load the background image int windowSize = max(width, height); int imageSize = min(bg.width, bg.height); int factor = ceil(2.5 * float(windowSize) / float(imageSize)); bgx = bg.width * factor; bgy = bg.height * factor; } }