/** Company: Shout3D LLC Project: Shout3D 2.0 Sample Code Class: ModDunkPanel Date: April 26, 1999 Description: Class for Panel that displays Mod Dunk Game (C) Copyright Shout3D LLC. - 1997-2001 - All rights reserved */ package applets; import java.applet.*; import shout3d.*; import shout3d.math.*; import shout3d.core.*; /** * ModDunk Game panel * * A little game. * Click the mouse down --> Ward Hole will pull his arm back * Release the mouse --> Ward Hole will let fly the plunger * * The object is to time the release so that the plunger hits the * target. If it does, an animation is played in which Jo falls into * the dunk tank. * * (Note: Cheaters can use the right mouse button to always win) * * @author Paul Isaacs * @author Jim Stewartson * @author Dave Westwood */ public class ModDunkPanel extends Shout3DPanel implements DeviceObserver { //{{ Shout3DApplet methods /** * Constructor */ public ModDunkPanel(Shout3DApplet applet){ super(applet); } // Pointers to handy nodes // Transform throwingArm; // USE Warh_Arm1_L Interpolator warh_Arm1_R_Interp; // interpolator for Warh_Arm1_R Interpolator warh_Arm2_R_Interp; // interpolator for Warh_Arm2_R Transform plungerRoot; // USE PLUNGER_ROOT Transform stuntPlunger; // USE STUNT_PLUNGER Transform target; // Use Prop_Target Transform targetMover; // Use Prop_Target_Mover // Interpolators for jo Interpolator jo_Hip_PosInterp; Interpolator jo_Hip_RotInterp; Interpolator jo_Leg2_L_RotInterp; Interpolator jo_Leg2_R_RotInterp; Interpolator jo_Chest_RotInterp; Interpolator jo_Arm1_L_RotInterp; Interpolator jo_Arm1_R_RotInterp; Interpolator prop_DivingBoard_RotInterp; //interpolator for Prop_DivingBoard Interpolator prop_DivingBoard_PosInterp; //interpolator for Prop_DivingBoard // Paths to handy paths Node stuntPlungerPath[]; /** * * This method is automatcially called by the parent class Shout3DPanel * at the correct time during initialize(). * * Subclasses should implement this to perform any custom initialization tasks. */ public void customInitialize() { // Load the audio for the gong. String gongSound = applet.getParameter("gongSound"); if (gongSound != null){ gong = applet.getAudioClip(applet.getCodeBase(), gongSound); } // To make it easy to find interpolators that effect specific // nodes, collect them all into a handy list collectInterpolators(); // Disconnect output of all time sensors. // Instead, we'll control the fractions of all interpolators // through the simulation. disconnectAllTimeSensors(); Node testNode; // Get pointers to interesting nodes // WARD HOLE character: // // Left Upper Arm, to rotate using physics if ((throwingArm = (Transform) getNodeByName("Warh_Arm1_L")) == null) throw new Shout3DException("could not find node named throwingArm"); // // Right Arm interpolators, to be controlled by simulation. if ((testNode = (Transform) getNodeByName("Warh_Arm1_R")) == null) throw new Shout3DException("could not find node named Warh_Arm1_R"); if ((warh_Arm1_R_Interp = findOrientationInterpolator(testNode)) == null) throw new Shout3DException("could not find node named Arm1_R_Interp"); if ((testNode = (Transform) getNodeByName("Warh_Arm2_R")) == null) throw new Shout3DException("could not find node named Warh_Arm2_R"); if ((warh_Arm2_R_Interp = findOrientationInterpolator(testNode)) == null) throw new Shout3DException("could not find node named warh_Arm2_R_Interp"); // PLUNGER // // Transform to use in placing the plunger if ((plungerRoot = (Transform) getNodeByName("PLUNGER_ROOT")) == null) throw new Shout3DException("could not find node named PLUNGER_ROOT"); // // This keeps track of where the plungerRoot should be // placed during the WAITING, WINDUP, and THROWING stages if ((stuntPlunger = (Transform) getNodeByName("STUNT_PLUNGER")) == null) throw new Shout3DException("could not find node named STUNT_PLUNGER"); // Get path to the stunt plunger, which will be required to // find transform between worldspace and the plunger // root node is stuntPlungerPath[0] and // stuntPlunger is stuntPlungerPath[stuntPlungerPath.length-1] Searcher mySearcher = getNewSearcher(); mySearcher.setNode(stuntPlunger); stuntPlungerPath = mySearcher.searchFirst(getScene()); // JO character // // Find the interpolators affected various nodes in Jo's body if ((testNode = (Transform)getNodeByName("Jo_Hip")) == null) throw new Shout3DException("could not find node named Jo_Hip"); jo_Hip_PosInterp = findPositionInterpolator(testNode); jo_Hip_RotInterp = findOrientationInterpolator(testNode); if ((testNode = (Transform)getNodeByName("Jo_Leg2_L")) == null) throw new Shout3DException("could not find node named Jo_Leg2_L"); jo_Leg2_L_RotInterp = findOrientationInterpolator(testNode); if ((testNode = (Transform)getNodeByName("Jo_Leg2_R")) == null) throw new Shout3DException("could not find node named Jo_Leg2_R"); jo_Leg2_R_RotInterp = findOrientationInterpolator(testNode); if ((testNode = (Transform)getNodeByName("Jo_Chest")) == null) throw new Shout3DException("could not find node named Jo_Chest"); jo_Chest_RotInterp = findOrientationInterpolator(testNode); if ((testNode = (Transform)getNodeByName("Jo_Arm1_L")) == null) throw new Shout3DException("could not find node named Jo_Arm1_L"); jo_Arm1_L_RotInterp = findOrientationInterpolator(testNode); if ((testNode = (Transform)getNodeByName("Jo_Arm1_R")) == null) throw new Shout3DException("could not find node named Jo_Arm1_R"); jo_Arm1_R_RotInterp = findOrientationInterpolator(testNode); // DIVING BOARD // if ((testNode = (Transform) getNodeByName("Prop_DivingBoard")) == null) throw new Shout3DException("could not find node named Prop_DivingBoard"); prop_DivingBoard_RotInterp = findOrientationInterpolator(testNode); prop_DivingBoard_PosInterp = findPositionInterpolator(testNode); // TARGET // if ((target = (Transform) getNodeByName("Prop_Target")) == null) throw new Shout3DException("could not find node named Prop_Target"); if ((targetMover = (Transform) getNodeByName("Prop_Target_Mover")) == null) throw new Shout3DException("could not find node named Prop_Target_Mover"); // Calculate center of target calculateTargetCenter(); // Register to receive deviceInput // Argument "DeviceInput" means to watch for all devices addDeviceObserver(this, "DeviceInput", null); getRenderer().addRenderObserver(this, null); } /** * Finalize */ protected void finalize() throws Throwable { myInterpolators = null; removeDeviceObserver(this,"DeviceInput"); // Call the parent class. // Technically not required since parent class' method does nothing. // But good practice because in classes derived from this one, // it will be necessary to call the superclass to get the // functionality herein. // Hence keeping the call here is good practice, so that if this // method is copied/pasted in a subclass, all will be well. super.finalize(); } //}} Shout3DApplet methods //{{ DeviceObserver methods /** * When mouse goes DOWN, Ward Hole's arm starts to move back. * When mouse goes UP, the arm is released. * * Pressing the 't' or 'T' key toggles the target's swaying motion. */ public boolean onDeviceInput(DeviceInput di, Object userData) { if (di.isOfType("MouseInput")) { MouseInput mi = (MouseInput) di; switch(mi.which) { case MouseInput.DOWN: return onMouseDown(mi.x, mi.y, mi.button); case MouseInput.UP: return onMouseUp(mi.x, mi.y, mi.button); } } else if (di.isOfType("KeyboardInput")) { KeyboardInput ki = (KeyboardInput) di; if (ki.which == KeyboardInput.PRESS) { return onKeyDown(ki.key); } } return false; } //}} Device response methods ///////////////////////////////////////////////////////// // Mod Dunk Game Specific Methods Begins here /** * Mouse goes down * * @param x the x position of the mouse down event * @param y the y position of the mouse down event */ public boolean onMouseDown(int x, int y, int whichButton){ // A click always pulls back the arm. // Release is timed to occur at releaseTime after the // release. pullBackArm(); // Pulling back with right mouse lets you win. alwaysAWinner = (whichButton == 1); // This panel always does something on mouse down... return true; } /** * Mouse goes up. * * @param x the x position of the mouse down event * @param y the y position of the mouse down event */ public boolean onMouseUp(int x, int y, int whichButton){ // release the arm. releaseArm(); return true; } /** * Key goes down. * * @param key the key */ public boolean onKeyDown(int key){ // The 't' key toggles the target motion. if (key == 't' || key == 'T'){ if (isTargetMoving) setTargetMoving(false); else setTargetMoving(true); } // Don't want to stop anything additional from happening, return false return false; } // When right mouse is used to throw, this is set to TRUE // and jo always dunks: boolean alwaysAWinner = false; // State of the Thrower // static final int WAITING = 0; static final int WINDUP = 1; static final int THROWING = 2; static final int AIRBORN = 3; int curThrowerState = WAITING; // State of poor little Jo's dunk boolean isNowDunking = false; float DUNK_DURATION = .666f; // Throwing arm properties // float windupAcceleration = -10; // Rate at which windup occurs on mouse down float throwCenterAngle = 4; // Resting angle of arm when not pulled float throwSpringK = 35; // Spring constant that brings arm to center float throwDamperK = 1; // Damping constant that affects arm float minWindupAngle = 1; // Farthest that arm will retreat when mouse is pressed and held. // Throwing arm state // // current values at any given time. float throwArmAngle = 4; float throwArmVelo = 0; float throwArmAccel = 0; // Tuning this number makes the arm go faster/slower when released. static final float PLUNGER_VELO_HACK_FACTOR = 1.2f; //1.5f; //2.0f; // Plunger state float plungerPos[] = { 0f, 0f, 0f }; float plungerVelo[] = { 0f, 0f, 0f }; float plungerAcc[] = { 0f, -9.8f, 0f }; // gravity downward float plungerRotXAngle = 0f; // For making 1D rotation easier. float plungerAngVelo = 0; float plungerAngAcc = 0; boolean isPlungerTestedPastTarget = false; // Target Properties float targetCenter[] = { 0f, 0f, 0f }; float targetRadiusSquared = 0.75f * 0.75f; float targetDepth = 0.1f; boolean isTargetMoving = false; double targetMotionStartTime = 0; float targetRotOmega = 1; float targetRotAmplitude = 1; float targetRotPhase = 0; // Time and timing // double prevTime = 0; double curTime = 0; float timeBetweenUpdates = 1; float maxTimeStep = 0.1f; double dunkStartTime = 0; double startThrowTime = 0; static final float THROW_TO_RELEASE_TIME = .26f; //.3f; // Sound java.applet.AudioClip gong; /** * This is called once per frame, and updates everything * based on what's happened so far. */ void updateGame() { // Animate the target if (isTargetMoving) moveTarget(); // Simulates motion of arm using physics. // If AIRBORN, will also simulate trajectory of plunger. // The result is a set of positions and orientations that // must then be put into the scene graph in the rest of this // method performSimulation(); // Sets the rotation of the throwing shoulder based on // rotation calculated in performSimulation. setThrowingArmRotation(); // Right arm is moved by setting the fraction of an // animation so that it is coordinated with the throwing arm's motion. animateFollowerArm(); if (curThrowerState != AIRBORN) { // Makes the plunger follow the stuntPlunger, a dummy transform // at the end of Ward Hole's arm. makePlungerHeldByWarhol(); } else { // Places the plunger based on simulated trajectory // calculated in performSimulation() makePlungerFollowTrajectory(); } // Is it time to switch from THROWING to AIRBORN? if (curThrowerState == THROWING) { if ((curTime - startThrowTime) > THROW_TO_RELEASE_TIME) releasePlunger(); } // If this is the frame where the target was hit, start the dunking! if (didPlungerHitTarget()) { startDunk(); // This makes the plunger bounce backwards plungerPos[2] = targetCenter[2]; plungerVelo[2] *= -0.5; plungerAngVelo *= -0.5; // After the first target hit, the // target starts animating: if (isTargetMoving == false) setTargetMoving(true); } if (isNowDunking) { animateDunk(); } } /** * Toggles whether the target is swaying back and forth. */ public void setTargetMoving(boolean onOff) { isTargetMoving = onOff; if (onOff) { targetMotionStartTime = getClock().getAbsoluteTime(); } else { // remember the phase as the time we're at now, so if // target is restarted, things will pick up nicely. float rotTime = (float)(targetRotPhase + (curTime - targetMotionStartTime)); targetRotPhase = rotTime; } } /** * Called when the target is moving to advance it based on * a sin function and the time passed since it started moving */ void moveTarget() { float rotTime = (float)(targetRotPhase + (curTime - targetMotionStartTime)); float targetAngle = (float) (targetRotAmplitude * Math.sin( targetRotOmega * rotTime)); // load axis/angle for rotation about z of targetAngle targetMover.rotation.getValue()[0] = 0; targetMover.rotation.getValue()[1] = 0; targetMover.rotation.getValue()[2] = 1; targetMover.rotation.getValue()[3] = targetAngle; //notify targetMover.rotation.setValue(targetMover.rotation.getValue()); } /** Simulates motion of arm using physics. * If AIRBORN, will also simulate trajectory of plunger. * The result is a set of positions and orientations that * must then be put into the scene graph in the rest of this * method */ void performSimulation() { // Save the time since last update. Other methods use this. timeBetweenUpdates = (float)(curTime - prevTime); // Execute simulation in small time steps until up to current time. // Required since one large timestep can wreak havoc, // especially when de-iconifying after a while. float myTimeBetween = timeBetweenUpdates; while ( myTimeBetween > 0 ) { if ( myTimeBetween < maxTimeStep ) performSimulationStep(myTimeBetween); else performSimulationStep(maxTimeStep); myTimeBetween = myTimeBetween - maxTimeStep; } } /** * Moves simulation forward in time by calculating an accelerations, * then advancing the positions and velocities accordingly. * */ void performSimulationStep(float deltaTime) { updateArmAcceleration(); doNumericalIntegration(deltaTime); constrainThrowingArm(); constrainPlunger(); } /** * Depending on the state of the arm, different accelerations are used. */ void updateArmAcceleration() { if (curThrowerState == WAITING) { // do nothing } else if (curThrowerState == WINDUP) { // rotate backwards by predetermined acceleration. throwArmAccel = windupAcceleration; } else if (curThrowerState == THROWING || curThrowerState == AIRBORN) { // Calculate acceleration based on standard simple spring/damper equation. throwArmAccel = - throwSpringK * (throwArmAngle - throwCenterAngle) - throwDamperK * throwArmVelo; } } /** * Given starting position/velocity/acceleration, find the position * and velocity at the end of the time step. * * Note to anyone who knows about Numerical Integration: * This is just plain Euler integration, error prone for * 'stiff' simulations. This simulation is set up to * not be stiff, though. * */ void doNumericalIntegration(float deltaTime) { // Calculate new angular velocity and angle for the throwing arm. throwArmVelo = throwArmVelo + throwArmAccel * deltaTime; throwArmAngle = throwArmAngle + throwArmVelo * deltaTime; // Only make the plunger fly if AIRBORN, else it is // positioned during makePlungerHeldByWarhol if (curThrowerState == AIRBORN) { // translation plungerVelo[0] = plungerVelo[0] + plungerAcc[0] * deltaTime; plungerVelo[1] = plungerVelo[1] + plungerAcc[1] * deltaTime; plungerVelo[2] = plungerVelo[2] + plungerAcc[2] * deltaTime; plungerPos[0] = plungerPos[0] + plungerVelo[0] * deltaTime; plungerPos[1] = plungerPos[1] + plungerVelo[1] * deltaTime; plungerPos[2] = plungerPos[2] + plungerVelo[2] * deltaTime; // rotation plungerAngVelo = plungerAngVelo + plungerAngAcc * deltaTime; plungerRotXAngle = plungerRotXAngle + plungerAngVelo * deltaTime; } } /** * The throwing arm is constrained not to rotate back further than minWindupAngle */ void constrainThrowingArm() { if (curThrowerState == WINDUP) { // constrain arm not to go too far back if (throwArmAngle < minWindupAngle) { throwArmAngle = minWindupAngle; // if forcing a stop, immediately decelerate throwArmVelo = 0; } } } // The boundaries of the playing area. // These are hardcoded based on knowledge of the game. float LEFT_WALL_X = -5; float RIGHT_WALL_X = 7; float NEAR_WALL_Z = -5; float FAR_WALL_Z = 6; /** * The plungers motion is reflected and damped whenever it hits * a wall, the floor, or the target. */ void constrainPlunger() { if (curThrowerState != AIRBORN) return; boolean flipAngVelo = false; if (plungerPos[1] < 0.0f) { // Plunger slipped under the floor. // Translation: // // Move up to floor level, reflect and damp the vertical velocity. plungerPos[1] = 0.0f; plungerVelo[1] = plungerVelo[1] * -.5f; // // when hitting the floor, friction damps sideways plungerVelo[0] = plungerVelo[0] * .95f; plungerVelo[2] = plungerVelo[2] * .95f; // Rotation: // when hitting the floor, flip and dampen angVelo plungerAngVelo *= -.5f; // // If plunger is now spinning slowly, start leveling it out // a bit: if (Math.abs(plungerAngVelo) < .5) { // First, normalize angle between -PI and +PI while(plungerRotXAngle > 3.1416) plungerRotXAngle -= 6.2832; while(plungerRotXAngle < -3.1416) plungerRotXAngle += 6.2832; // Now, work like a gentle spring towards horizontal. if (plungerRotXAngle > 0f) { plungerAngVelo -= 1 * (plungerRotXAngle - 1.57079); } else { plungerAngVelo -= 1 * (plungerRotXAngle + 1.57079); } } } // If a wall is hit, just reflect and dampen the translational velocity // if (plungerPos[0] < LEFT_WALL_X) { plungerPos[0] = LEFT_WALL_X; plungerVelo[0] = plungerVelo[0] * -.5f; // no change in angular velo since flight || to x-walls } if (plungerPos[0] > RIGHT_WALL_X) { plungerPos[0] = RIGHT_WALL_X; plungerVelo[0] = plungerVelo[0] * -.5f; // no change in angular velo since flight || to x-walls } if (plungerPos[2] < NEAR_WALL_Z) { plungerPos[2] = NEAR_WALL_Z; plungerVelo[2] = plungerVelo[2] * -.5f; flipAngVelo = true; } if (plungerPos[2] > FAR_WALL_Z) { plungerPos[2] = FAR_WALL_Z; plungerVelo[2] = plungerVelo[2] * -.5f; flipAngVelo = true; } if (flipAngVelo) { plungerAngVelo *= -.5f; } } // These are hardcoded based on what looks good float RIGHT_ARM_MIN_FRACTION = 0.25f; float RIGHT_ARM_MAX_FRACTION = 1.0f; /** * Right arm is moved by setting the fraction of an * animation so that it is coordinated with the throwing arm's motion. */ void animateFollowerArm() { // Ramps animation fraction between [RIGHT_ARM_MAX_FRACTION,RIGHT_ARM_MIN_FRACTION] // to match the throwing arms' value in the range [throwCenterAngle,minWindupAngle] float animationFraction = 0.0f; if (throwArmAngle > throwCenterAngle) { animationFraction = RIGHT_ARM_MIN_FRACTION; } else if (throwArmAngle < minWindupAngle) { animationFraction = RIGHT_ARM_MAX_FRACTION; } else { animationFraction = (throwArmAngle - throwCenterAngle) / (minWindupAngle - throwCenterAngle); animationFraction = RIGHT_ARM_MAX_FRACTION * animationFraction + RIGHT_ARM_MIN_FRACTION; } // Set this fraction in animation of both joints of the right arm. warh_Arm1_R_Interp.fraction.setValue(animationFraction); warh_Arm2_R_Interp.fraction.setValue(animationFraction); } /** * Sets the rotation of the throwing shoulder based on * rotation calculated in performSimulation. */ void setThrowingArmRotation() { // Load rotation of throwArmAngle, about -x axis into throwingArm throwingArm.rotation.getValue()[0] = -1; throwingArm.rotation.getValue()[1] = 0; throwingArm.rotation.getValue()[2] = 0; throwingArm.rotation.getValue()[3] = throwArmAngle; throwingArm.rotation.setValue(throwingArm.rotation.getValue()); } /** Edits the plungerRoot (which lives in world space) * so that it matches the location and rotation of * stuntPlunger (which lives in the space at the end of warhol's * arm) */ void makePlungerHeldByWarhol() { //Check this to avoid divide by 0 //Shouldn't happen, but to be safe... if (timeBetweenUpdates == 0f) return; // Convert the origin and y direction from // stuntPlunger space to world space, matrix by matrix // Node curNode; float p[] = { 0f, 0f, 0f }; float y[] = { 0f, 1f, 0f }; float mtx[]; for (int i = stuntPlungerPath.length - 1; i >=0; i--) { if (stuntPlungerPath[i] instanceof Transform) { mtx = ((Transform) stuntPlungerPath[i]).getMatrix(); MatUtil.multVecMatrix( mtx, p ); MatUtil.multDirMatrix( mtx, y ); } } // Set the translation from the transformed point plungerRoot.translation.getValue()[0] = p[0]; plungerRoot.translation.getValue()[1] = p[1]; plungerRoot.translation.getValue()[2] = p[2]; //notify plungerRoot.translation.setValue(plungerRoot.translation.getValue()); // Set the rotation as a rotation about the x axis, based on how the // Calculate the X rotation required to rotate (0,1,0) into // alignment with y MatUtil.normalize(y); // Get angle between worldspace y and transformed y. float dotprod = y[1]; // Dot product with (0,1,0) is just y value. plungerRotXAngle = (float) Math.acos(dotprod); // Sign of z tells which direction: if (y[2] < 0) plungerRotXAngle *= -1; // Now set the rotation field. plungerRoot.rotation.getValue()[0] = 1; plungerRoot.rotation.getValue()[1] = 0; plungerRoot.rotation.getValue()[2] = 0; plungerRoot.rotation.getValue()[3] = plungerRotXAngle; plungerRoot.rotation.setValue(plungerRoot.rotation.getValue()); // Update the plunger velocities (translation and rotational) // needed to move forward when the hand lets go of the plunger. // // Calculate based on deltaPos/deltaT, multiplied by a handy factor // that lets us change the timing of the plunger so it's easier to // control. plungerVelo[0] = (p[0]-plungerPos[0]) * PLUNGER_VELO_HACK_FACTOR / timeBetweenUpdates; plungerVelo[1] = (p[1]-plungerPos[1]) * PLUNGER_VELO_HACK_FACTOR / timeBetweenUpdates; plungerVelo[2] = (p[2]-plungerPos[2]) * PLUNGER_VELO_HACK_FACTOR / timeBetweenUpdates; // Match plunger angular velocity to that of arm plungerAngVelo = -throwArmVelo; // Save this to reflect the new position. plungerPos[0] = p[0]; plungerPos[1] = p[1]; plungerPos[2] = p[2]; } /** * Places the plunger based on simulated trajectory * calculated in performSimulation() */ void makePlungerFollowTrajectory(){ plungerRoot.translation.getValue()[0] = plungerPos[0]; plungerRoot.translation.getValue()[1] = plungerPos[1]; plungerRoot.translation.getValue()[2] = plungerPos[2]; //notify plungerRoot.translation.setValue(plungerRoot.translation.getValue()); plungerRoot.rotation.getValue()[0] = 1; plungerRoot.rotation.getValue()[1] = 0; plungerRoot.rotation.getValue()[2] = 0; plungerRoot.rotation.getValue()[3] = plungerRotXAngle; //notify plungerRoot.rotation.setValue(plungerRoot.rotation.getValue()); } /** * Calculates current worldspace location of target center. * Since target may animate, this can be different each frame. * This is done based on knowledge that there are only two * transforms affecting the target, (target and targetMover) */ void calculateTargetCenter() { // Start with 0,0,0 which is center of target, targetCenter[0] = 0f; targetCenter[1] = 0f; targetCenter[2] = 0f; // Transform by target's tranfsorm MatUtil.multVecMatrix( target.getMatrix(), targetCenter); // Transform by targetMover's transform MatUtil.multVecMatrix( targetMover.getMatrix(), targetCenter); } /** * Determines if the plunger hit the target during this last time step. */ boolean didPlungerHitTarget() { boolean result = false; if (isPlungerTestedPastTarget == false) { // Only compare if plunger has just past the depth of the target center. // Target moves only in xy plane, so this is exactly when the // test needs to be done. if (plungerPos[2] < (targetCenter[2] + targetDepth)) { if (alwaysAWinner) { result = true; } else { calculateTargetCenter(); // Is the plunger position within the targetRadius of the // target's center? float xdist = plungerPos[0] - targetCenter[0]; float ydist = plungerPos[1] - targetCenter[1]; float dist2d = xdist * xdist + ydist * ydist; if ( dist2d < targetRadiusSquared ) { result = true; } } isPlungerTestedPastTarget = true; } } return result; } /** * Gets the dunk started */ void startDunk() { isNowDunking = true; dunkStartTime = curTime; if (gong != null) gong.play(); } /** * Determines the fraction to apply to all timers in the dunk animation. */ void animateDunk() { float fraction = (float)((curTime - dunkStartTime)/ DUNK_DURATION); if (fraction > 1.0f) fraction = 1.0f; if (fraction < 0.0f) fraction = 0.0f; setDunkInterpolators(fraction); } /** * Visits all dunk timers and sets to the given fraction. */ void setDunkInterpolators(float fraction) { jo_Hip_PosInterp.fraction.setValue(fraction); jo_Hip_RotInterp.fraction.setValue(fraction); jo_Leg2_L_RotInterp.fraction.setValue(fraction); jo_Leg2_R_RotInterp.fraction.setValue(fraction); jo_Chest_RotInterp.fraction.setValue(fraction); jo_Arm1_L_RotInterp.fraction.setValue(fraction); jo_Arm1_R_RotInterp.fraction.setValue(fraction); prop_DivingBoard_RotInterp.fraction.setValue(fraction); prop_DivingBoard_PosInterp.fraction.setValue(fraction); } /** * Called when the mouse first clicks down. */ void pullBackArm() { // change state, initialize prevTime for this turn curThrowerState = WINDUP; // Reset Dunktank isNowDunking = false; setDunkInterpolators(0.0f); } /** * Called when THROW_RELEASE_TIME has passed since the * mouse was released. */ void releasePlunger() { // change state, advance prev time curThrowerState = AIRBORN; // Just released, so we'll want a new shot at target isPlungerTestedPastTarget = false; } /** * Called when the mouse is released. * The plunger stays attached until * THROW_RELEASE_TIME has passed, then releasePlunger() * will be called during updateGame() */ void releaseArm() { curThrowerState = THROWING; startThrowTime = curTime; } /** * * Called each frame. Updates time, then calles updateGame * * Note that no onPostRender() method is implemented in this class, * even though it is required for all classes implementing RenderObserver. * This is because the super class implements RenderObserver fully * and onPostRender is inherited. * Hence this class needs only to override the onPreRender() method, * making sure to call super.onPreRender(r,userData) within the body * of the method. * */ public void onPreRender(Renderer r, Object userData){ // Call the parent class. // Technically not required since parent class' method does nothing. // But good practice because in classes derived from this one, // it will be necessary to call the superclass to get the // functionality herein. // Hence keeping the call here is good practice, so that if this // method is copied/pasted in a subclass, all will be well. super.onPreRender(r, userData); boolean wasTimeZero = (curTime == 0); prevTime = curTime; curTime = getClock().getAbsoluteTime(); // System.out.println("wasTimeZero = " + wasTimeZero); if ( !wasTimeZero ) { updateGame(); } } Interpolator[] myInterpolators = null; // Collects all the interpolators in the scene and puts the list into myInterpolators void collectInterpolators() { Searcher mySearcher = getNewSearcher(); mySearcher.setType("Interpolator"); Node[][] allPaths = mySearcher.searchAll(getScene()); if (allPaths == null) return; myInterpolators = new Interpolator[allPaths.length]; for (int i = 0; i < allPaths.length; i++) { if (allPaths[i] == null) myInterpolators[i] = null; else myInterpolators[i] = (Interpolator) allPaths[i][allPaths[i].length-1]; } } TimeSensor tmpSensor; void disconnectAllTimeSensors() { Searcher mySearcher = getNewSearcher(); mySearcher.setType("TimeSensor"); Node[][] allPaths = mySearcher.searchAll(getScene()); if (allPaths == null) return; for (int i = 0; i < allPaths.length; i++) { tmpSensor = (TimeSensor) allPaths[i][allPaths[i].length-1]; for (int j = tmpSensor.fraction.getNumRoutes()-1; j >= 0; j--){ tmpSensor.fraction.deleteRoute(tmpSensor.fraction.getRoutedField(j)); } } } // Returns interpolator that affects forNode. // Second argument is whether or not to set the timeSensor of the // interpolator to null public Interpolator findOrientationInterpolator(Node forNode){ if ( !(forNode instanceof Transform)) return null; Transform xf = (Transform) forNode; for (int i = 0; i < myInterpolators.length;i++) { if (myInterpolators[i] instanceof OrientationInterpolator){ if ( isRouted(((OrientationInterpolator)myInterpolators[i]).value, xf.rotation)){ return myInterpolators[i]; } } } return null; } public Interpolator findPositionInterpolator(Node forNode){ if ( !(forNode instanceof Transform)) return null; Transform xf = (Transform) forNode; for (int i = 0; i < myInterpolators.length;i++) { if (myInterpolators[i] instanceof PositionInterpolator){ if ( isRouted(((PositionInterpolator)myInterpolators[i]).value, xf.translation)){ return myInterpolators[i]; } } } return null; } }