GG.camera().posZ( 10 ); GG.camera().orthographic(); GG.scene().backgroundColor( @(0,0,0) ); GG.fullscreen(); .75 => GG.scene().light().intensity; Mouse mouse; spork ~ mouse.start(0); spork ~ mouse.selfUpdate(); Gain main => JCRev rev => dac; // main bus .1 => main.gain; 0.1 => rev.mix; 14 => int NUM_SEGMENTS; 1 => float BAG_SIZE; -6 => float BELT_START_X_POS; .15 => float SPAWN_PERSON_PROB; .15 => float FOUND_BAG_PROB; .004 @=> float SEGMENT_TRANSLATE_RATE; .05 @=> float PLAYHEAD_TRANSLATE_RATE; GG.nextFrame() => now; GG.frameWidth() => float screenWidth; GG.frameHeight() => float screenHeight; [2.5, 1.25, 0, -.5] @=> float yPos[]; 0 => int mouseDown; 0 => int mouseUp; 0 => int timeFrozen; [ Color.RED, Color.ORANGE, Color.GOLD, Color.GREEN, Color.BLUE, Color.VIOLET, Color.PURPLE, Color.WHITE] @=> vec3 colors[]; GScene scene; Bag activeLuggage[0]; PlaceHolderBag placeHolders[0]; fun void MakeSign() { FileTexture tex; tex.path(me.dir() + "data/sign.png"); GPlane p1 --> scene; p1.mat().diffuseMap(tex); p1.pos(@(-5,-2,-1)); p1.sca(@(2, 1.5, 2)); GPlane p2 --> scene; p2.mat().diffuseMap(tex); p2.pos(@(5,-2,-1)); p2.sca(@(2, 1.5, 2)); } MakeSign(); fun GPlane[] ConstructBelt(float startX, int yPosIdx, int direction) { GG.dt() => float dt; GPlane belt[NUM_SEGMENTS]; float x; for (int i; i < NUM_SEGMENTS; i++) { i => x; belt[i].mat().polygonMode(Material.POLYGON_LINE); belt[i].sca(@(BAG_SIZE, BAG_SIZE, BAG_SIZE)); belt[i] --> scene; @(startX + x * direction, yPos[yPosIdx], mouse.worldPos.z) => vec3 pos; belt[i].pos(pos); spork ~ MoveSegment(belt[i], direction, yPosIdx, GG.dt()); } return belt; } ConstructBelt(BELT_START_X_POS, 0, 1) @=> GPlane belt1[]; ConstructBelt(-BELT_START_X_POS, 1, -1) @=> GPlane belt2[]; ConstructBelt(BELT_START_X_POS, 2, 1) @=> GPlane belt3[]; //ConstructBelt(-BELT_START_X_POS, 3, -1) @=> GPlane belt4[]; fun void MoveSegment(GPlane segment, int direction, int yPosIdx, float dt) { while (true) { if (timeFrozen) { GG.nextFrame() => now; continue; } (BELT_START_X_POS - BAG_SIZE) * -direction => float thresh; segment.translateX(SEGMENT_TRANSLATE_RATE * direction * 1.1); if (CrossedThresh(segment, thresh, direction)) { @(-thresh, yPos[yPosIdx], mouse.worldPos.z) => vec3 pos; segment.pos(pos); } GG.nextFrame() => now; } } fun int CrossedThresh(GGen segment, float currThresh, int direction) { if (direction == 1 && segment.pos().x > currThresh) return 1; else if (direction == -1 && segment.pos().x < currThresh) return 1; return 0; } fun void MakePlayHeads() { PlayHead p1 --> scene; p1.pos(@(BELT_START_X_POS, yPos[0], mouse.worldPos.z)); p1.color(1,0,0); spork ~ RunPlayHead(p1, 1, 0); PlayHead p2 --> scene; p2.pos(@(BELT_START_X_POS, yPos[1], mouse.worldPos.z)); p2.color(0,1,0); spork ~ RunPlayHead(p2, -1, 1); PlayHead p3 --> scene; p3.pos(@(BELT_START_X_POS, yPos[2], mouse.worldPos.z)); p3.color(0,0,1); spork ~ RunPlayHead(p3, 1, 2); /* PlayHead p4 --> scene; p4.pos(@(BELT_START_X_POS, yPos[3], mouse.worldPos.z)); p4.color(0,1,1); spork ~ RunPlayHead(p4, -1, 3); */ } MakePlayHeads(); fun void MovePlayHead(PlayHead p, int direction, int idx) { while (true) { if (timeFrozen) { GG.nextFrame() => now; continue; } (BELT_START_X_POS - BAG_SIZE) * -direction => float thresh; p.translateX(PLAYHEAD_TRANSLATE_RATE * direction); if (CrossedThresh(p, thresh, direction)) { @(-thresh, yPos[idx], mouse.worldPos.z) => vec3 pos; p.pos(pos); } GG.nextFrame() => now; } } fun void RunPlayHead(PlayHead p, int direction, int idx) { spork ~ MovePlayHead(p, direction, idx); while (true) { p.pos().x + p.line.sca().x => float rightBound; p.pos().x - p.line.sca().x => float leftBound; if (timeFrozen) { GG.nextFrame() => now; continue; } for (Bag bag : activeLuggage) { if (bag.pos().x >= leftBound && bag.pos().x <= rightBound && bag.pos().y == p.pos().y) { spork ~ bag.Play(); } } GG.nextFrame() => now; } } fun void SpawnPlaceHolderBags() { -3.5 => float startX; for (int i; i < colors.size(); i++) { PlaceHolderBag p --> scene; colors[i] @=> p.color; p.p.mat().color(colors[i]); p.pos(@(startX + i, -2.5, 9)); placeHolders << p; } } SpawnPlaceHolderBags(); fun void PickUpLuggage() { for (int i; i < placeHolders.size(); i++) { if (MouseOverBag(placeHolders[i])) { 1 => timeFrozen; Bag newBag --> scene; newBag.sca(@(BAG_SIZE, BAG_SIZE, BAG_SIZE)); newBag.SetMaterial(placeHolders[i].color); FollowMouse(newBag); break; } } for (int i; i < activeLuggage.size(); i++) { if (MouseOverBag(activeLuggage[i])) { 1 => timeFrozen; FollowMouse(activeLuggage[i]); break; } } } fun int MouseOverBag(GGen bag) { bag.pos() => vec3 bagPos; mouse.worldPos.x => float mouseX; mouse.worldPos.y => float mouseY; if (mouseX >= bagPos.x - BAG_SIZE / 2 && mouseX <= bagPos.x + BAG_SIZE / 2 && mouseY >= bagPos.y - BAG_SIZE / 2 && mouseY <= bagPos.y + BAG_SIZE / 2) { return 1; } return 0; } fun void FollowMouse(Bag bag) { while (mouseDown) { bag.pos(@(mouse.worldPos.x, mouse.worldPos.y, mouse.worldPos.z)); GG.nextFrame() => now; } DropLuggage(bag); } fun void DropLuggage(Bag bag) { 0 => timeFrozen; for (GPlane segment : belt1) { if (bag.TryAttatchToSegment(segment)) return; } for (GPlane segment : belt2) { if (bag.TryAttatchToSegment(segment)) return; } for (GPlane segment : belt3) { if (bag.TryAttatchToSegment(segment)) return; } /* for (GPlane segment : belt4) { if (bag.TryAttatchToSegment(segment)) return; } */ } fun void ModifyActiveLuggageIdxs(Bag removedBag) { removedBag.activeLuggageIdx => int removedIdx; removedIdx + 1 => int i; for (i; i < activeLuggage.size(); i++) { activeLuggage[i].activeLuggageIdx - 1 => activeLuggage[i].activeLuggageIdx; } activeLuggage.popOut(removedIdx); } // Mouse Class class Mouse { int mouseState[3]; Event mouseDownEvents[3]; // need to pass 1 frame to propagate correct frame dimensions from render thread GG.nextFrame() => now; 1.0 => float SPP; // framebuffer to screen pixel ratio // workaround for mac retina displays if (GG.frameWidth() != GG.windowWidth()) { 2.0 => float SPP; } 0 => static int LEFT_CLICK; 1 => static int RIGHT_CLICK; 1.0 => float mouseZ; // mouse depth in world space vec3 worldPos; // start this device (should be sporked) fun void start(int device) { // HID input and a HID message Hid hi; HidMsg msg; // open mouse 0, exit on fail if( !hi.openMouse( device ) ) { cherr <= "failed to open device " + device <= IO.newline(); me.exit(); } <<< "mouse '" + hi.name() + "' ready", "" >>>; // infinite event loop while( true ) { hi => now; while( hi.recv( msg ) ) { // mouse button down if( msg.isButtonDown() ) { 1 => mouseState[msg.which]; mouseDownEvents[msg.which].broadcast(); 1 => mouseDown; 0 => mouseUp; spork ~ PickUpLuggage(); } // mouse button up else if( msg.isButtonUp() ) { 0 => mouseState[msg.which]; 0 => mouseDown; 1 => mouseUp; } } } } // update mouse world position fun void selfUpdate() { while (true) { GG.mouseX() => float x; GG.mouseY() => float y; GG.frameWidth() / SPP => float screenWidth; GG.frameHeight() / SPP => float screenHeight; // calculate mouse world X and Y coords if (GG.camera().mode() == GCamera.ORTHO) { // calculate screen aspect screenWidth / screenHeight => float aspect; // calculate camera frustrum size in world space GG.camera().viewSize() => float frustrumHeight; // height of frustrum in world space frustrumHeight * aspect => float frustrumWidth; // width of frustrum in world space // convert from normalized mouse coords to view space coords // (we negate viewY so that 0,0 is bottom left instead of top left) frustrumWidth * (x / screenWidth - 0.5) => float viewX; -frustrumHeight * (y / screenHeight - 0.5) => float viewY; // convert from view space coords to world space coords GG.camera().posLocalToWorld(@(viewX, viewY, -mouseZ)) => worldPos; } else { // perspective // generate ray going from camera through click location GG.camera().screenCoordToWorldRay(x, y) => vec3 ray; // calculate spawn position by moving along ray mouseZ * ray + GG.camera().posWorld() => worldPos; } GG.nextFrame() => now; } } } class PlayHead extends GGen { FlatMaterial mat; GPlane line --> this; line.mat(mat); mat.color(@(1, 1, 1)); @(0.05, 1, 0) => line.sca; 4 => float pulseRate; fun void color(float r, float g, float b) { mat.color(@(r, g, b)); // set material color } } class PlaceHolderBag extends GGen { FileTexture tex; tex.path(me.dir() + "data/bag.png"); GPlane p --> this; p.mat().diffuseMap(tex); vec3 color; } class Bag extends GGen { FileTexture tex; tex.path(me.dir() + "data/bag.png"); GPlane p --> this; p.mat().diffuseMap(tex); vec3 color; int activeLuggageIdx; float freq; 0 => int followingSegment; 0 => int transcending; 0 => int spawnedPerson; 0 => int inActiveLuggage; 0 => int isConnected; GPlane currentSegment; TriOsc s; Envelope e => main; [62, 64, 66, 67, 69, 71, 73, 74] @=> int notes[]; fun void Play() { this.sca(@(BAG_SIZE + .25,BAG_SIZE + .25,BAG_SIZE + .25)); if (!isConnected) { 1 => isConnected; s => e; } freq => s.freq; 100::ms => e.duration; e.keyOn(); 100::ms => now; e.keyOff(); this.sca(@(BAG_SIZE, BAG_SIZE, BAG_SIZE)); } fun void StayAlive() { while (true) { GG.nextFrame() => now; } } fun void SpawnPerson() { 1 => spawnedPerson; Person p --> scene; p.pos(@(Math.random2f(-5,5), -3, 0)); this @=> p.targetBag; p.SetMaterial(color); } fun int TryAttatchToSegment(GPlane segment) { segment.pos() => vec3 segmentPos; this.pos() => vec3 bagPos; if (bagPos.x >= segmentPos.x - BAG_SIZE / 2 && bagPos.x <= segmentPos.x + BAG_SIZE / 2 && bagPos.y >= segmentPos.y - BAG_SIZE / 2 && bagPos.y <= segmentPos.y + BAG_SIZE / 2) { for (Bag activeBag : activeLuggage) { if (activeBag.pos() == segmentPos) return 0; } if (!inActiveLuggage) { activeLuggage << this; activeLuggage.size() - 1 => activeLuggageIdx; 1 => inActiveLuggage; } segment @=> currentSegment; 1 => followingSegment; Math.random2f(0,1) => float p; if (!spawnedPerson && p <= SPAWN_PERSON_PROB) SpawnPerson(); StayAlive(); return 1; } return 0; } fun void SetFreq() { if (color == colors[0]) Std.mtof(notes[0]) => freq; if (color == colors[1]) Std.mtof(notes[1]) => freq; if (color == colors[2]) Std.mtof(notes[2]) => freq; if (color == colors[3]) Std.mtof(notes[3]) => freq; if (color == colors[4]) Std.mtof(notes[4]) => freq; if (color == colors[5]) Std.mtof(notes[5]) => freq; if (color == colors[6]) Std.mtof(notes[6]) => freq; if (color == colors[7]) Std.mtof(notes[7]) => freq; } fun void SetMaterial(vec3 c) { p.mat().color(c); p.mat().polygonMode(Material.POLYGON_FILL); c @=> color; SetFreq(); } fun void update(float dt) { if (followingSegment && !timeFrozen && !transcending) { this.pos(@(currentSegment.pos().x, currentSegment.pos().y, 8)); } } } class Person extends GGen { GPlane body --> this; GCircle head --> this; GCircle leftEye --> this; GCircle rightEye --> this; vec3 myColor; Bag targetBag; leftEye.sca(@(.06,.06,.06)); rightEye.sca(@(.06,.06,.06)); leftEye.pos(@(-.08,.5,0)); rightEye.pos(@(.08,.5,0)); body.sca(@(.3, .3, .3)); head.sca(@(.25, .25, .25)); head.pos(@(0,.4,0)); .5 => float thresh; 0 => int foundBag; fun void Transcend() { ModifyActiveLuggageIdxs(targetBag); 1 => targetBag.transcending; spork ~ PlayTranscendSound(); this.sca() => vec3 personScale; targetBag.sca() => vec3 bagScale; this.pos(@(0,-5,-3)); targetBag.pos(@(0,-5,-2)); targetBag.sca(this.sca()); for (float i; i < 1000; i+1 => i) { i / 200 => float scaleFactor; this.sca(@(personScale.x + scaleFactor, personScale.y + scaleFactor, personScale.z)); targetBag.sca(@(bagScale.x + scaleFactor, bagScale.y + scaleFactor, bagScale.z)); this.pos(@(this.pos().x, this.pos().y + i / 10000, this.pos().z)); targetBag.pos(@(targetBag.pos().x, targetBag.pos().y + i / 10000, targetBag.pos().z)); GG.nextFrame() => now; } targetBag --< scene; this --< scene; } fun void PlayTranscendSound() { SndBuf buf => Gain g => dac; me.dir() + "data/angel.wav" => buf.read; .5 => g.gain; while (g.gain() > 0) { g.gain() - .01 => g.gain; 65::ms => now; } } fun void PlayNotMyBagSound() { SndBuf buf => Gain g => dac; Math.random2f(0,1) => float p; me.dir() + "data/nope.wav" => buf.read; 1 => g.gain; 3::second => now; } fun void SetMaterial(vec3 color) { this.posZ(mouse.worldPos.z); head.mat().color(color); body.mat().color(color); leftEye.mat().color(Color.BLACK); rightEye.mat().color(Color.BLACK); color @=> myColor; } fun vec3 CalculateDirectionToBag() { this.pos() @=> vec3 ourPos; targetBag.pos() @=> vec3 bagPos; return bagPos - ourPos; } fun float DistanceToTarget() { return Math.euclidean(this.pos(), targetBag.pos()); } fun void ChangeBag() { if (activeLuggage.size() <= 1) { this --< scene; me.exit(); } while (true) { Math.random2(0, activeLuggage.size() - 1) => int bagIdx; if (activeLuggage[bagIdx] != targetBag && activeLuggage[bagIdx].color != myColor) { activeLuggage[bagIdx] @=> targetBag; SetMaterial(activeLuggage[bagIdx].color); 0 => foundBag; me.exit(); } GG.nextFrame() => now; } } fun void update(float dt) { if (!timeFrozen) { CalculateDirectionToBag() @=> vec3 direction; this.translate(direction / 110); if (DistanceToTarget() <= thresh && !foundBag) { 1 => foundBag; Math.random2f(0, 1) => float p; if (p <= FOUND_BAG_PROB) { spork ~ Transcend(); } else { spork ~ PlayNotMyBagSound(); spork ~ ChangeBag(); } } } } } // Game Loop while (true) { GG.nextFrame() => now; //<<< "Active luggage size:", activeLuggage.size() >>>; }